J C*_*per 12 ocaml types idioms
我是第一次看OCaml,有一些F#和Haskell的背景知识.因此,很多东西都很熟悉,但有一点不是"开放"和"封闭"联合的概念(使用反引号和[<语法]).
这些有用的是什么以及它们使用的次数是多少?
Gil*_*il' 19
gasche的回答有很好的建议.我将更多地解释开放和封闭的工会.
首先,您需要区分两种联合:基本变体(没有反引号)和多态变体(使用反引号).
M1和M2你有不同的类型.M1.Foo并且M2.Foo是不同的构造者.`Foo无论你在哪里使用它都是相同的构造函数.多态变体类型描述了类型可能具有的构造函数.但是许多多态变体类型并不完全清楚 - 它们包含(行)类型变量.考虑空列表[]:它的类型是'a list,它可以在许多上下文中使用,它们分配更多特定类型'a.例如:
# let empty_list = [];;
val empty_list : 'a list = []
# let list_of_lists = [] :: empty_list;;
val list_of_lists : 'a list list = [[]]
# let list_of_integers = 3 :: empty_list;;
val list_of_integers : int list = [3]
行类型变量也是如此.编写的open类型[> … ]具有行变量,可以在每次使用该值时实例化该行变量以覆盖更多构造函数.
# let foo = `Foo;;
val foo : [> `Foo ] = `Foo
# let bar = `Bar;;
val bar : [> `Bar ] = `Bar
# let foobar = [foo; bar];;
val foobar : [> `Bar | `Foo ] list = [`Foo; `Bar]
仅仅因为构造函数出现在类型中并不意味着该类型的每次使用都必须允许所有构造函数.[> …]说类型必须至少包含这些构造函数,并且双重地[< …]说类型必须至多具有这些构造函数.考虑这个功能:
# let h = function `Foo -> `Bar | `Bar -> `Foo;;
val h : [< `Bar | `Foo ] -> [> `Bar | `Foo ] = <fun>
h仅能够处理Foo和Bar,所以输入类型可能不允许其他的构造; 但是可以调用h只允许的类型Foo.相反,h可以返回Foo或Bar,并且使用的任何上下文h必须允许两者Foo和Bar(并且可以允许其他人).
当类型的最小和最大构造函数要求匹配时,会出现封闭类型.例如,让我们添加h必须具有相同输入和输出类型的约束:
# let hh : 'a -> 'a = function `Foo -> `Bar | `Bar -> `Foo;;
val hh : [ `Bar | `Foo ] -> [ `Bar | `Foo ] = <fun>
封闭类型很少从类型推断中自然产生.大多数时候,就像这里一样,它们是用户注释的结果.当您使用多态注释时,最好定义命名类型并至少在每个顶级函数中使用它们.否则,推断类型可能会比您想象的更加通用.虽然这很少打开通向bug的方法,但这通常意味着任何类型的错误都会在以后的时间内被诊断出来,并且会产生很长的错误消息,很难找到有用的位.
我建议阅读和处理(即重新键入顶层中的示例,稍微玩一下以确保您理解每一步)Ocaml手册中的多态变体教程.
gas*_*che 14
您需要阅读Jacques Garrigue的"具有多态变体的代码重用":
http://www.math.nagoya-u.ac.jp/~garrigue/papers/fose2000.html
多态变体的问题在于它们非常灵活,类型推断无法帮助您解决多态变体代码中的错误.例如,如果您输错了一个构造函数名称,则编译器将无法标记错误,它只会使用通常的构造函数和拼写错误的构造函数推断出稍微不同的类型.只有在您尝试将错误代码与对变量进行严格假设的函数(封闭模式匹配)组合时,才会发现错误,并且会显示错误的错误消息.
我对多态变体用户的建议是大量使用注释来控制类型检查:每次将多态变量作为输入或输出一个时,该函数应该使用变量部分的精确类型进行注释.这将使您免受大多数推理问题的影响,并强制您构建一组富有表现力的类型定义,这些类型定义可以组合并帮助推理您的程序.