除了安全之外,还有强大打字的好处吗?

时间:2016-03-21 22:00:00

标签: haskell types functional-programming type-systems dependent-type

在Haskell社区中,我们正在慢慢添加依赖类型的功能。依赖类型是一种高级类型功能,类型可以依赖于值。像Agda和Idris这样的语言已经拥有它们。它似乎是一个非常高级的功能,需要一个高级类型系统,直到你意识到python 有依赖类型有依赖类型的动态类型,可能是也可能不是实际的依赖类型,从一开始。

对于函数式编程语言中的大多数程序,无论打字有多高级,都有一种方法可以将其作为无类型的lambda演算术语重复出现。这是因为打字只会删除程序,而不会启用新程序。

强力打字赢得了我们的安全。运行时发生的错误类别在运行时不再发生。这种安全性非常好。除了这种安全性之外,强力打字能带给你什么?

除安全性之外,强类型系统还有其他好处吗?

(请注意,我并不是说强打字是没有价值的。安全本身就是一个巨大的好处。我只是想知道是否还有额外的好处。)

4 个答案:

答案 0 :(得分:25)

首先,我们需要谈谈简单输入的lambda演算的历史。

简单类型的lambda演算有两个历史发展。

  • 当Alonzo Church描述lambda演算时,这些类型作为术语的含义/操作行为的一部分被烘焙。

  • 当Haskell Curry描述lambda演算时,类型是注释的注释。

所以我们有lambda演算教堂和lambda演算la Curry。有关详情,请参阅https://en.wikipedia.org/wiki/Simply_typed_lambda_calculus#Intrinsic_vs._extrinsic_interpretations

具有讽刺意味的是,以库里命名的Haskell语言是基于lambda演算的教堂!

这意味着类型不仅仅是为您排除不良程序的注释。他们也可以“做事”。这些类型不会在不留下残留物的情况下擦除。

这表现在Haskell的类型类概念中,这就是为什么Haskell是一种教会语言的原因。

在Haskell中,当我创建一个函数时

sort :: Ord a => [a] -> [a]

我们将Ord a的对象或字典作为第一个参数传递。

但是你并没有被迫在代码中探讨自己的论点,编译器的工作就是构建它并使用它。

instance Ord Char
instance Ord Int
instance Ord a => Ord [a]

因此,如果你去对字符串列表进行排序,这些字符串本身是字符列表,那么这将通过将Ord Char实例传递给Ord a =>的实例来构建字典。 Ord [a]获取Ord [Char],它与Ord String相同,然后你可以对字符串列表进行排序。

调用上面的sort比通过将LexicographicComparator<List<Char>>传递给它的构造函数并使用额外的第二个参数调用函数来手动构建IComparator<Char>要简单得多,如果我是比较在Haskell中调用这样的sort函数到在C#或Java中调用它的复杂性。

这向我们表明,使用类型进行编程可能会明显减少冗长,因为类型检查期间,implicits和类型类等机制可以为程序推断出大部分代码

在一个更简单的基础上,即使参数的大小也可以取决于类型,除非你想支付相当大的成本来用你的语言装箱所有内容,以便它具有同质的表示。

这向我们展示了使用类型编程可以显着提高效率,因为它可以使用专用表示,而不是在代码中的任何地方支付盒装结构。 int不能只是一个机器整数,因为它必须看起来像系统中的其他所有东西。如果您愿意在运行时放弃一个数量级或更高性能,那么这对您来说无关紧要。

最后,一旦我们为我们提供“做事”的类型,考虑仅仅安全提供的重构效益通常是有益的。

如果我重构剩下的较小代码集,它将为我重写所有类型类管道。它将找出它可以重写代码以取消更多参数的新方法。我不会手工制作所有这些东西,我可以将这些平凡的任务留给类型检查器。

但是即使我确实改变了类型,我也可以毫不犹豫地移动参数,因为编译器很可能会发现我的错误。类型为您提供“自由定理”,就像整类此类错误的单元测试一样。

另一方面,一旦我用Python这样的语言锁定API,我就会害怕改变它,因为它会在运行时默默地为所有下游依赖项而破坏!这导致了巴洛克式的API,它们很容易依赖于容易腐烂的关键字参数,并且随着时间的推移,随着时间的推移而发展的API的内容很少类似于你开箱即用的内容。因此,即使仅仅是安全问题,一旦您希望人们在您的工作之上进行构建,而不是简单地在它过于笨拙时更换它,就会对API设计产生长期影响。

答案 1 :(得分:21)

  

这是因为打字只会删除程序,而不会启用新程序。

这不是一个正确的陈述。类型类使得从类型级信息生成程序的一部分成为可能。

考虑两个表达式:

  1. readMaybe "15" :: Maybe Integer
  2. readMaybe "15" :: Maybe Bool
  3. 我在这里使用readMaybe模块中的Text.Read函数。在术语级别,这些表达式是相同的,只有它们的类型注释是不同的。但是,它们在运行时生成的结果不同(第一种情况为Just 15,第二种情况为Nothing

    这是因为编译器会根据您的静态类型信息为您生成代码。更确切地说,它选择一个合适的类型类实例并将其字典传递给多态函数(在我们的例子中为readMaybe)。

    这个例子很简单,但有更复杂的用例。使用mtl库,您可以编写在不同计算上下文中运行的计算(又名Monad s)。编译器将自动插入大量管理计算上下文的代码。在动态类型语言中,您无法获得静态信息。

    正如您所看到的,静态类型不仅会切断不正确的程序,还会为您编写正确的程序。

答案 2 :(得分:3)

重构。通过使用强类型系统,您可以安全地重构代码,让编译器告诉您现在正在做的事情是否有意义。打字系统越强,避免的重构错误就越多。这当然意味着您的代码更易于维护。

答案 3 :(得分:3)

当您已经知道自己想要写什么以及如何写作时,您需要“安全”。这是什么类型有用的很小一部分。关于类型最重要的是它们使你的推理结构化。当有人用Python addr写作时,他没有看到a + ba作为一些抽象变量 - 他将它们视为一些数字。类型已经存在于人类的内部语言中,Python只是没有类型系统来讨论它们。 “类型化与无类型(统一)编程”争议中的实际问题是“我们是否希望以安全,明确或不安全和隐含的方式反映我们的内部结构化概念?”。类型不会引入新概念 - 它是无类型推理会忘记现有概念。

当有人看到一棵树(我的意思是一个真正的绿色树)时,他并没有看到它上面的每一片叶子,但他并没有将它视为一个抽象的无名对象。 “树” - 对于大多数情况来说是一个足够好的近似值,这就是为什么我们有Hindley-Milner类型的系统,但有时你想谈论一个特定的树,你确实想要看叶子。这就是依赖类型给你的东西:缩放的能力。 “没有叶子的树”,“森林中的一棵树”,“特定形式的树”......依赖类型编程只是人类思考方式的又一步。

在一个不太抽象的说明中,我有一个类型检查器,用于依赖玩具类型的语言,其中所有键入规则都表示为data type的构造函数。您无需深入了解类型检查过程即可了解系统规则。这就是“缩放”的力量:你可以根据需要引入复杂的不变量,从而区分必要部分和不重要部分。

功率相关类型的另一个例子是给你各种形式的反射。看,例如在Pierre-ÉvaristeDagandthesis,证明了

  

泛型编程只是编程

当然,类型是提示,我定义的许多函数和抽象我会用弱类型语言以更笨拙的方式定义,但类型提示更好的替代方案。

毫无疑问“选择什么:简单类型或依赖类型?”。依赖类型总是更好,它们当然包含简单类型。问题是“选择什么:没有类型或依赖类型?”,但这个问题不适合我。