是什么使Haskell类型系统如此受人尊敬(相对于Java)?


204

我开始学习Haskell。我对此很陌生,我只是在阅读一些在线书籍,以了解其基本结构。

熟悉它的人经常谈论的“模因”之一就是整个“如果它可以编译,它将可以工作*”的东西-我认为这与类型系统的强度有关。

我试图理解为什么Haskell在这方面到底比其他静态类型的语言要好。

换句话说,我假设在Java中,您可以做一些令人讨厌的ArrayList<String>()事情,例如埋葬, 以包含确实应该包含的内容ArrayList<Animal>()。这里令人讨厌的事情是您的stringcontains elephant, giraffe等,如果有人放进去,Mercedes您的编译器将无法为您提供帮助。

如果这样做了,ArrayList<Animal>()那么在以后的某个时间点,如果我确定我的程序不是真正的关于动物的,而是关于车辆的,那么我可以更改,例如产生ArrayList<Animal>生产的功能,ArrayList<Vehicle>而我的IDE应该告诉我那里的所有地方是一个编译中断。

我的假设是,这就是人们所说的类型系统的含义,但是对我而言,为什么Haskell的更好才是显而易见的。换句话说,您可以编写好的Java或坏的Java,我假设您可以在Haskell中执行相同的操作(即,将东西填入字符串/整数中,它们实际上应该是一流的数据类型)。

我怀疑我缺少重要/基本的东西。
我很高兴能向我展示我的方式的错误!


31
我会让人们比写真实的答案更有知识,但是要点是:像C#这样的静态类型的语言都有一个试图帮助您编写可防御代码的类型系统;诸如Haskell试图帮助您编写正确(即可证明)代码的类型系统。工作的基本原则是将可以检查的内容移到编译阶段。Haskell 在编译时检查更多的东西。
罗伯特·哈维

8
我对Haskell不太了解,但是我可以谈论Java。尽管它看起来是强类型的,但它仍然允许您按照您所说的做“令人发指的事情”。对于Java对其类型系统所做的几乎所有保证,都有一种解决方法。

12
我不知道为什么所有答案Maybe都只在最后提到。如果我只选择一件事,那就是更多流行的语言应该从Haskell借用。这是一个非常简单的想法(从理论上讲不是很有趣),但是仅此一项就可以使我们的工作变得如此轻松。
Paul

1
这里会有很好的答案,但是为了帮助您研究类型签名。它们允许人类和程序以某种方式来推理程序,这些方式将说明Java如何处于糊状的中间re:类型。
迈克尔·复活节

6
为了公平起见,我必须指出,“如果编译的话,它将起作用的整体”是一个口号,而不是对事实的字面陈述。是的,我们的Haskell程序员知道,对于某些有限的正确性概念,通过类型检查器会带来很大的正确性机会,但这肯定不是字面意义上普遍的“真实”声明!
汤姆·埃利斯

Answers:


230

这是Haskell中可用的类型系统功能的无序列表,或者在Java中不可用或不太好(据我所知,这在Java方面是很弱的)

  • 安全。Haskell的类型具有非常好的“类型安全”属性。这是非常具体的,但是从本质上讲,它意味着某种类型的值不能随意转换为另一种类型。有时这与可变性不一致(请参阅OCaml的值限制
  • 代数数据类型。Haskell中的类型与高中数学基本上具有相同的结构。事实证明,这是极其简单和一致的,然而,它却像您可能想要的那样强大。它只是类型系统的重要基础。
    • 数据类型通用编程。这与泛型类型不同(请参见generalization)。相反,由于如上所述的类型结构的简单性,编写通常在该结构上运行的代码相对容易。稍后,我将讨论EqHaskell编译器如何针对用户定义的类型自动推导出uality之类的内容。从本质上讲,它的实现方法是遍历任何用户定义类型基础的通用简单结构,并在值之间进行匹配-这是一种非常自然的结构相等形式。
  • 相互递归类型。这只是编写非平凡类型的必要组成部分。
    • 嵌套类型。这使您可以定义以不同类型递归的变量的递归类型。例如,一种平衡树是data Bt a = Here a | There (Bt (a, a))。仔细考虑的有效值,Bt a并注意该类型如何工作。这很棘手!
  • 概括。这在类型系统中几乎没有,太愚蠢了(哼,看着你,走)。重要的是要具有类型变量的概念,并且能够谈论与该变量的选择无关的代码。辛德雷米尔纳是从系统F. Haskell的类型系统派生的类型系统是HM打字的阐述和系统F基本上是炉床概括的。我的意思是说Haskell有一个很好的概括性故事。
  • 抽象类型。Haskell在这里的故事不是很好,但也不是不存在的。可以编写具有公共接口但具有私有实现的类型。这样一来,我们就可以在以后接受对实现代码的更改,并且重要的是,由于它是Haskell中所有操作的基础,因此请编写具有明确定义的接口(例如)的“魔术”类型IO。坦白地说,Java实际上可能有一个更好的抽象类型故事,但是我不认为直到接口变得更流行才是真的。
  • 参数化。Haskell值没有任何通用运算。Java在引用相等和哈希等方面违反了这一原则,在强制方面则更为公然。这意味着您可以获得关于类型的自由定理,这些定理使您可以完全从其类型完全了解操作或值的含义-某些类型只能有很少的居民。
  • 当编码棘手的事物时,类型较高的类型会显示所有类型。Functor / Applicative / Monad,Foldable / Traversable,整个mtl效果输入系统,广义functor定点。清单一直在继续。有很多东西可以更好地表达为高级种类,而相对很少的类型系统甚至可以让用户谈论这些东西。
  • 类型类。如果您将类型系统视为逻辑(这很有用),那么通常会要求您证明事物。在许多情况下,这本质上是线路噪声:可能只有一个正确的答案,这对于程序员来说是浪费时间和精力。类型类是Haskell为您生成证明的一种方式。更具体地讲,这使您可以解决简单的“类型方程式系统”,例如“我们打算将哪种类型的(+)事物放在一起?哦Integer,好吧!现在就内联正确的代码!”。在更复杂的系统上,您可能会建立更多有趣的约束。
    • 约束演算。Haskell中的约束(进入类型类序言系统的机制)在结构上是类型化的。这提供了一种非常简单的子类型关系形式,使您可以从较简单的约束中组合出复杂的约束。整个mtl库都基于这个想法。
    • 派生。为了提高类型类系统的规范性,有必要编写许多通常不重要的代码来描述用户定义的类型必须实例化的约束。对于Haskell类型的非常普通的结构,通常可以要求编译器为您做此样板。
    • 输入class prolog。Haskell类型类求解器(一种生成我之前提到的“证明”的系统)本质上是Prolog的一种残缺形式,具有更好的语义特性。这意味着您可以在prolog类型中编码真正繁琐的事物,并希望它们在编译时全部处理。一个很好的例子可能是解决一个证明,即如果您忘记顺序,则两个异构列表是等效的-它们是等效的异构“集合”。
    • 多参数类型类和功能依赖项。这些只是对基本typeclass序言的大量有用的改进。如果您了解Prolog,则可以想象当您编写多个变量的谓词时,表达能力会提高多少。
  • 很好的推断。基于Hindley Milner类型系统的语言具有很好的推论。HM本身具有完整的推论,这意味着您无需编写类型变量。Haskell 98是Haskell的最简单形式,已经在一些非常罕见的情况下将其排除。通常,现代的Haskell一直在尝试逐步减少完整推理的空间,同时为HM增加更多功能并查看用户何时抱怨。人们很少抱怨-Haskell的推论非常好。
  • 仅非常非常非常弱的子类型。我在前面提到过,类型类序言中的约束系统具有结构子类型的概念。那是Haskell中唯一的子类型形式。对于推理和推论,子类型化很糟糕。这使得每个问题都变得更加困难(一个不平等的系统而不是一个平等的系统)。这也很容易造成误解(子类化与子类型化一样吗?
    • 请注意,最近(2017年初),史蒂文·多兰(Steven Dolan)在MLsub上发表了他的论文MLsub是ML和Hindley-Milner类型推论的变体,具有很好的子类型故事(另请参见)。这并没有消除我在上面所写的内容-大多数子类型系统都已损坏并且具有错误的推理-但确实表明我们直到今天才发现一些有希望的方法可以使完整的推理和子类型完美地结合在一起。现在,要完全清楚,Java的子类型化概念绝不能利用Dolan的算法和系统。它需要重新考虑子类型的含义。
  • 更高等级的类型。我谈到推广较早,但不仅仅是单纯的概括它能够谈论已经广义变量的类型有用更在其中。例如,对那些“包含”的结构不了解的高阶结构之间的映射(请参阅参数性)具有的类型(forall a. f a -> g a)。在直接HM中,您可以使用这种类型编写函数,但对于高级类型,则需要这样的函数作为参数,例如:mapFree :: (forall a . f a -> g a) -> Free f -> Free g。注意,a变量仅在参数内绑定。这意味着函数的定义者mapFree可以决定a使用它们时实例化的对象,而不是的用户mapFree
  • 存在类型。较高级别的类型使我们可以讨论通用量化,而存在性类型则使我们可以讨论存在性量化:仅存在一些满足某些方程式的未知类型的想法。这最终很有用,并且需要花费较长时间才能继续进行。
  • 类型家庭。有时类型类机制很不方便,因为我们并不总是在Prolog中考虑。类型族让我们编写类型之间的直接功能关系。
    • 封闭式家庭。默认情况下,类型族是开放的,这很烦人,因为这意味着尽管可以随时扩展它们,但是却无法“反转”它们并获得成功的希望。这是因为您不能证明内射性,但可以使用封闭型家族。
  • 实物索引类型和类型提升。在这一点上,我真的变得很异国情调,但是这些有时会被实际使用。如果您想编写一种打开或关闭的句柄,那么您可以做得很好。请注意以下片段中State的一个非常简单的代数类型,其值也被提升为类型级别。然后,随后,我们可以谈论类型构造就像Handle是在特定的拍摄参数的种类一样State。了解所有细节令人困惑,但也非常正确。

    data State = Open | Closed
    
    data Handle :: State -> * -> * where
      OpenHandle :: {- something -} -> Handle Open a
      ClosedHandle :: {- something -} -> Handle Closed a
  • 运行时类型表示有效。Java因具有类型擦除功能而臭名昭著,在某些人的游行中雨淋淋。类型擦除正确的选择,但是,就好像您拥有一个函数一样,getRepr :: a -> TypeRepr那么至少您会违反参数性。更糟糕的是,如果这是一个用户生成的函数,该函数用于在运行时触发不安全的强制措施,那么您将面临巨大的安全隐患。Haskell的Typeable系统允许创建一个保险箱coerce :: (Typeable a, Typeable b) => a -> Maybe b。该系统依赖于Typeable在编译器中实现(而不是在Userland中实现),并且如果没有Haskell的类型机制和保证遵循的法律,也无法获得如此好的语义。

但是,Haskell的类型系统的价值不仅与这些有关,还与类型如何描述语言有关。这是Haskell的一些功能,这些功能通过类型系统来驱动价值。

  • 纯度。Haskell对“副作用”的定义非常非常非常宽泛,没有任何副作用。这将迫使你把更多的信息分为不同的类型,因为类型的管理输入和输出,且无副作用一切都在投入和产出必须考虑。
    • IO。随后,Haskell需要一种讨论副作用的方法-因为任何实际程序都必须包含某些副作用-所以类型类,更高种类的类型和抽象类型的组合产生了使用特定的超特殊类型(称为IO a代表)的概念产生类型为的值的副作用计算a。这是嵌入纯语言内部的非常好的效果系统的基础。
  • 缺乏null。众所周知,这null是现代编程语言的十亿美元错误。代数类型,特别是通过将类型A转换为类型而将“不存在”状态附加到所拥有的类型上的能力Maybe A,从而完全缓解的问题null
  • 多态递归。这使您可以定义用于归纳类型变量的递归函数,尽管在每个递归调用中以自己的归纳将它们用于不同类型的情况下也可以使用它们。这很难谈论,但是对于谈论嵌套类型特别有用。回顾一下以前的Bt a类型,并尝试编写一个函数来计算其大小:size :: Bt a -> Int。看起来有点像size (Here a) = 1size (There bt) = 2 * size bt。从操作上讲并不太复杂,但是请注意,size最后一个方程式中对的递归调用是在不同的类型上发生的,但是总体定义具有很好的广义类型size :: Bt a -> Int。请注意,这是一个破坏总推断的功能,但是如果您提供类型签名,则Haskell会允许它。

我可以继续,但是这个清单应该可以让您入门然后再入手。


7
空值不是十亿美元的“错误”。在某些情况下,在任何有意义的可能存在之前,无法静态地验证不会取消引用指针; 在这种情况下具有尝试解除引用陷阱的方法通常比要求指针最初标识无意义的对象要好。我觉得最大的空相关的错误是有实现它,因为,将在陷阱,但不会产生陷阱也不char *p = NULL;*p=1234char *q = p+5678;*q = 1234;
supercat

37
托尼·霍尔(Tony Hoare)直言不讳:en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retractions。尽管我确定有时null指针算术是必要的,但我将其解释为说指针算术是承载您语言语义的一个不好的地方,而不是null仍然不是一个错误。
J. Abrahamson

18
@supercat,您确实可以编写不带null的语言。是否允许是一个选择。
Paul Draper

6
@supercat-Haskell中也存在该问题,但形式不同。Haskell通常是懒惰的并且是不变的,因此p = undefined只要您p不进行评估,就可以编写。更有用的是,undefined只要您不对其进行评估,就可以放入某种可变的引用。更为严重的挑战是可能无法终止的惰性计算,这当然是无法确定的。主要区别在于,这些都是明确的编程错误,并且从不用于表示普通逻辑。
Christian Conkle 2015年

6
@supercat Haskell完全缺少引用语义(这是引用透明性的概念,它意味着通过用引用替换其引用来保留所有内容)。因此,我认为你的问题是不恰当的。
J. Abrahamson

78
  • 完整类型推断。实际上,您可以无所不在地使用复杂的类型,而无需像“老兄,我所做的就是编写类型签名”。
  • 类型是完全代数的,这使得表达一些复杂的思想非常容易。
  • Haskell具有类型类,它们类似于接口,只是您不必将一个类型的所有实现都放在同一位置。您可以为现有的第三方类型创建自己的类型类的实现,而无需访问其源代码。
  • 高阶和​​递归函数倾向于将更多功能放入类型检查器的范围。以filter为例。在命令式语言中,您可能会编写一个for循环来实现相同的功能,但是您将没有相同的静态类型保证,因为for循环没有返回类型的概念。
  • 子类型的缺乏极大地简化了参数多态性。
  • 较高类型的类型(类型的类型)在Haskell中相对易于指定和使用,它使您可以围绕Java中完全无法理解的类型创建抽象。

7
好的答案-您能给我一个简单的高类型类型的例子,以为这将帮助我理解为什么在Java中是不可能做到的。
phatmanace

3
有一些很好的例子在这里
Karl Bielefeldt

3
模式匹配也非常重要,这意味着您可以使用对象的类型来轻松做出决策。
Benjamin Gruenbaum 2015年

2
@BenjaminGruenbaum我认为我不会将其称为类型系统功能。
Doval 2015年

3
虽然抽象数据类型和HKTS是definetly我怀疑任何人问这个问题是要知道为什么他们是有用的答案的一部分,我认为,这两个部分需要扩大解释这个
JK。

62
a :: Integer
b :: Maybe Integer
c :: IO Integer
d :: Either String Integer

在Haskell中:整数,可能为null的整数,其值来自外界的整数以及可能为字符串的整数都是截然不同的类型- 编译器将强制执行此操作。您不能编译不遵守这些区别的Haskell程序。

(但是,您可以省略类型声明。在大多数情况下,编译器可以为变量确定最通用的类​​型,这将导致编译成功。这不是很整洁吗?)


11
+1虽然这个答案还不完整,但我认为最好将其放在问题级别
jk。

1
+1虽然可以帮助解释其他语言Maybe(例如Java Optional和Scala Option)也有帮助,但是在这些语言中,这是半熟的解决方案,因为您始终可以分配null给该类型的变量,并使程序在运行时爆炸-时间。Haskell [1]不会发生这种情况,因为没有null值,因此您根本无法作弊。([1]:实际上,您可以使用部分函数(例如,具有fromJust时,产生与NullPointerException相似的错误Nothing,但这些函数可能会被皱眉))。
Andres F.

2
“其值来自外部世界的整数”-会不会IO Integer更接近“子程序,该子程序在执行时会给出整数”?由于a)main = c >> c第一个返回的值c可能与第二个返回的值不同,ca不论其位置如何(只要我们处于单个范围内),其值都相同b)有一些类型表示来自外部的值来对其进行卫生处理(即不要直接放置它们,而是首先检查用户输入是否正确/不是恶意的)。
Maciej Piechotka 2015年

4
Maciej,那会更准确。我努力追求简单。
WolfeFan 2015年

30

许多人列出了有关Haskell的好东西。但是在回答您的特定问题“为什么类型系统使程序更正确?”时,我怀疑答案是“参数多态性”。

考虑以下Haskell函数:

foobar :: x -> y -> y

字面上只有一个可能的方式来实现这一功能。仅仅通过类型签名,我可以告诉正是这个函数做什么,因为只有一个可能的事情是可以做的。[好,不是,但是差不多!]

停下来思考一下。这实际上是一件大事!这意味着,如果我使用此签名编写一个函数,则该函数实际上除了我想要的目的外不可能做任何事情。(当然,类型签名本身仍然可能是错误的。没有一种编程语言可以阻止所有错误。)

考虑以下功能:

fubar :: Int -> (x -> y) -> y

此功能是不可能的。您实际上无法实现此功能。我可以从类型签名中看出这一点。

如您所见,Haskell类型签名告诉您很多东西!


与C#比较。(对不起,我的Java有点生锈了。)

public static TY foobar<TX, TY>(TX in1, TY in2)

此方法可以完成几件事:

  • 返回in2结果。
  • 永远循环,永不返回任何东西。
  • 引发异常,并且永不返回任何东西。

实际上,Haskell也具有这三个选项。但是C#还为您提供了其他选项:

  • 返回空值。(Haskell没有空值。)
  • 修改in2后再返回。(Haskell没有就地修改。)
  • 使用反射。(Haskell没有反射。)
  • 返回结果之前,请执行多个I / O操作。(除非在此处声明要执行I / O,否则Haskell不允许您执行I / O。)

反思是一个特别大的锤子。使用反射,我可以TY凭空建造一个新对象,然后将其返回!我可以检查两个对象,并根据发现的内容执行不同的操作。我可以对传入的两个对象进行任意修改。

I / O同样是重锤。该代码可能是向用户显示消息,或者打开数据库连接,或者重新格式化硬盘或其他任何东西。


foobar相比之下,Haskell 函数只能获取一些数据并返回该数据,而不会发生变化。它无法“查看”数据,因为其类型在编译时未知。它无法创建新数据,因为...好吧,您如何构造任何可能类型的数据?您需要对此进行反思。它不能执行任何I / O,因为类型签名没有声明正在执行I / O。因此它无法与文件系统或网络交互,甚至无法在同一程序中运行线程!(即100%保证线程安全。)

如您所见,Haskell 通过不允许您做很多事情来让您对代码的实际作用做出非常有力的保证。实际上,如此紧密,以至于(对于真正的多态代码而言)通常只有一种可能的方式将各个部分组合在一起。

(要明确:在类型签名不能告诉您太多信息的情况下,仍然可以编写Haskell函数。Int -> Int可能几乎是任何事情。但是即使那样,我们仍然知道相同的输入将始终以100%的确定性产生相同的输出。 Java甚至都不保证!)


4
+1好答案!这是非常强大的功能,并且经常被Haskell的新手所忽视。顺便说一句,一个更简单的“不可能”功能是fubar :: a -> b,不是吗?(是的,我知道unsafeCoerce我假设我们不是在名称中的“不安全”谈论任何事情,也不应该新人担心:!d)
安德烈斯F.

有很多您不能编写的简单类型签名,是的。例如,这foobar :: x是无法实现的……
MathematicalOrchid

实际上,您不能使纯代码成为线程不安全的,但仍可以使它成为多线程的。您的选择是“在评估之前,先评估”,“在评估时,您可能还希望在一个单独的线程中评估”,以及“在评估时,您可能也希望在另外一个线程中评估”在单独的线程中”。默认值为“随心所欲”,这实际上意味着“尽早评估”。
John Dvorak

更一般地说,您可以在in1或in2上调用具有副作用的实例方法。或者,您可以修改全局状态(这是理所当然的,在Haskell中建模为IO操作,但可能不是大多数人认为的IO)。
Doug McClean 2015年

2
@isomorphismes类型x -> y -> y是完全可实现的。类型(x -> y) -> y不是。该类型x -> y -> y接受两个输入,然后返回第二个。该类型(x -> y) -> y具有上操作的函数x,并且必须以某种方式使该函数y ...
MathematicalOrchid

17

一个相关的SO问题

我假设您可以在haskell中做同样的事情(即将东西塞进真正应该是一流数据类型的字符串/整数中)

不,您确实不能-至少不能以Java的相同方式。在Java中,这种情况会发生:

String x = (String)someNonString;

Java会很乐意尝试将非String转换为String。Haskell不允许这种事情,从而消除了整个运行时错误类。

null是类型系统(如Nothing)的一部分,因此需要明确地要求和处理,从而消除了其他所有类别的运行时错误。

我还有很多其他微妙的好处-特别是在重用和类型类方面-我没有足够的专业知识来进行交流。

不过,主要是因为Haskell的类型系统具有很多表现力。您仅需遵循几个规则就可以完成很多工作。考虑一下一直存在的Haskell树:

data Tree a = Leaf a | Branch (Tree a) (Tree a) 

您已经用相当容易理解的一行代码定义了整个通用二叉树(和两个数据构造函数)。所有这些都只使用一些规则(具有求和类型和产品类型)。在Java中,这是3-4个代码文件和类。

尤其是在那些倾向于崇敬类型的系统中,这种简洁/优雅得到了高度重视。


我从您的答案中仅了解NullPointerExceptions。您能否提供更多示例?
Jesvin Jose

2
不一定正确,JLS§5.5.1如果T是类类型,则| S | <:| T |或| T | <:| S |。否则,将发生编译时错误。因此,编译器不允许您转换不可转换的类型-显然有很多解决方法。
蜘蛛鲍里斯(Boris the Spider)

我认为,利用类型类优势的最简单方法是,它们就像interfaces,可以在事后添加,并且它们不会“忘记”实现它们的类型。也就是说,您可以确保函数的两个参数具有相同的类型,与interfaces 不同(其中List<String>s可能具有不同的实现)。从技术上讲,您可以通过在每个接口中添加类型参数来在Java中执行非常相似的操作,但是99%的现有接口不执行此操作,并且会使您的同行感到困惑。
2015年

2
@BoristheSpider为True,但是强制转换异常几乎总是涉及从超类向下转换为子类或从接口向下转换为类,并且超类通常不是Object
2015年

2
我认为关于字符串的问题的重点与强制转换和运行时类型错误无关,而是这样的事实,即如果您不想使用类型,Java不会使您-实际上,数据以序列化方式存储形式,滥用字符串作为临时any类型。Haskell也不会阻止您这样做,因为...嗯,它有字符串。Haskell可以为您提供工具,但如果您坚持要求Greenspunning足够多的解释器以在嵌套上下文中进行重新发明,Haskell不会强行阻止您执行愚蠢的事情null。没有语言可以。
Leushenko

0

熟悉它的人经常谈论的“模因”之一就是整个“如果它可以编译,它将可以工作*”的东西-我认为这与类型系统的强度有关。

对于小程序,这通常是正确的。Haskell可以防止您犯其他语言中容易犯的错误(例如,将“ an” Int32和“ a” Word32和“ some”爆炸),但不能防止您犯所有错误。

哈斯克尔并实际上使重构一个很多更容易。如果您的程序以前是正确的并且可以进行类型检查,则很有可能在经过较小的修改后仍会正确。

我试图理解为什么Haskell在这方面确实比其他静态类型的语言好。

Haskell中的类型相当轻巧,因为很容易声明新类型。这与Rust这样的语言相反,Rust的所有内容都比较麻烦。

我的假设是,这就是人们所说的强类型系统的含义,但是对我而言,为什么Haskell的更好才是显而易见的。

Haskell除了简单的求和和乘积类型外,还具有许多功能。它也具有通用量化的类型(例如id :: a -> a)。您还可以创建包含函数的记录类型,该记录类型与Java或Rust等语言完全不同。

GHC还可以仅基于类型派生某些实例,并且自从泛型问世以来,您就可以编写类型之间具有泛型的函数。这非常方便,并且比Java中的使用起来更为流利。

另一个区别是Haskell倾向于具有相对较好的类型错误(至少在撰写本文时)。Haskell的类型推断非常复杂,很少需要提供类型注释才能进行编译。这与Rust相反,Rust中,即使编译器原则上可以推断出类型,类型推断有时也可能需要注释。

最后,Haskell具有类型类,其中包括著名的monad。Monad恰好是一种处理错误的好方法。它们基本上为您提供了几乎所有的便利,null而无需进行可怕的调试,也不会放弃任何类型的安全性。因此,在鼓励我们使用它们时,在这些类型上编写函数的能力实际上很重要!

换句话说,您可以编写好的或不好的Java,我想您可以在Haskell中进行相同的操作

也许是对的,但它缺少一个关键点:在Haskell中开始用脚射击的点比在Java中开始用脚射击的点更远。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.