Cha*_*ers 13 c# f# haskell types algebraic-data-types
我是一名正在通过"真实世界Haskell"工作的C#开发人员,以便真正理解函数式编程,因此当我学习F#时,我真的会理解它,而不仅仅是"在F#中编写C#代码",可以这么说.
好吧,今天我偶然发现了一个例子,我认为我已经理解了3次不同的时间,然后才看到我错过的东西,更新我的解释,然后递归(并诅咒,相信我).
现在我相信我确实理解它,并且我在下面写了详细的"英语解释".你可以请哈斯克尔大师确认理解,或指出我错过了什么?
注意:Haskell代码片段(直接引自本书)定义了一种自定义类型,该类型与内置的Haskell列表类型同构.
Haskell代码片段
data List a = Cons a (List a)
| Nil
defining Show
Run Code Online (Sandbox Code Playgroud)
编辑:经过一些回应后,我看到了一个误解,但我不太清楚Haskell"解析"规则会纠正这个错误.所以我在下面列出了我原来的(不正确的)解释,然后进行了修正,接着是我仍然不清楚的问题.
编辑:这是我原来的(不正确的)"片段的"英语解释"
更正的解释如下(BOLD的变化)
如果使用"Nil"值构造函数,则没有字段.
5.(此行已被删除...它不准确)"Cons"值构造函数具有单个类型参数.
如果使用"Cons"值构造函数,则必须提供2个字段.第一个必填字段是a的实例.第二个必填字段是"List-of-a"的实例.
这个问题目前还不清楚
最初的混淆是关于片段的部分"Cons a(List a)".事实上,这是我仍然不清楚的部分.
人们已经指出"Cons"标记之后的行上的每个项目都是一个类型,而不是一个值.所以这意味着这一行说"Cons值构造函数有两个字段:一个是'a'类型,另一个是'list-of-a'."
知道这非常有帮助.但是,有些事情仍然不清楚.当我使用Cons值构造函数创建实例时,这些实例将第一个'a'解释为"将值传递到此处".但他们并没有以同样的方式解释第二个'a'.
例如,考虑一下这个GHCI会议:
*Main> Cons 0 Nil
Cons 0 Nil
*Main> Cons 1 it
Cons 1 (Cons 0 Nil)
*Main>
Run Code Online (Sandbox Code Playgroud)
当我输入"Cons 0 Nil"时,它使用"Cons"值构造函数来创建List的实例.从0开始,它知道类型参数是"整数".到目前为止,没有混乱.
然而,它也确定该值的缺点的第一个字段值为0.然而,它决定什么对价值的第二个字段的......它只是确定第二场有一个类型 "列表整数"的.
所以我的问题是,为什么"一个"在第一场的意思是"这个字段的类型是'A’ 和这个字段的值是'A’",而"一"在第二场意味着只有 "类型这个字段是'列表'"?
编辑:我相信我已经看到了光明,感谢几个回应.让我在这里阐述一下.(如果以某种方式它仍然是不正确的,请务必告诉我!)
在片段"Cons a(List a)"中,我们说"Cons"值构造函数有两个字段,第一个字段的类型为"a",第二个字段的类型为"List of a" ".
这就是我们所说的!特别是,我们都在说NOTHING约值!这是我失踪的一个关键点.
稍后,我们想要使用"Cons"值构造函数创建一个实例.我们在解释器中键入:"Cons 0 Nil".这明确告诉Cons值构造函数使用0作为第一个字段的值,并使用Nil作为第二个字段的值.
这就是它的全部内容.一旦你知道值构造函数定义只指定类型,一切都变得清晰.
感谢大家的有益回应.正如我所说,如果还有任何问题,请务必告诉我.谢谢.
- "Cons"值构造函数具有单个类型参数.
不,你宣布时已经参数化了data List a.一个有效的属性是,如果我有一个Nil :: List Int,我不能用Nil :: List Char交换它.
- 如果使用"Cons"值构造函数,则必须提供2个字段.第一个必填字段是List的实例.第二个必填字段是a的实例.
你已经交换了它:第一个必填字段是a的实例,第二个字段是List的实例.
真实世界Haskell的这一章可能会引起关注.
谢谢.那是我现在的章节.所以...当代码说"Cons a(List a)"时,我认为"Cons a"的一部分是声明Cons值构造函数是参数化的.它们还没有涵盖参数化类型的语法,所以我猜想如果你打算使用a语法必须重新声明"a".但你说这不是必要的吗?因此,这不是"a"的意思吗?
不.一旦我们在我们的类型中声明了一个参数,我们就会重复使用它,否则说"应该在那里使用那种类型".它有点像a -> b -> a类型签名:a是参数化类型,但是我必须使用相同的a作为返回值.
好的,但这很令人困惑.似乎第一个"a"意味着"第一个字段是一个实例",
不,那不是真的.它只是意味着数据类型参数化了某些类型a.
它也意味着"第一个字段的值与它们传入的值相同".换句话说,它指定类型AND值.
不,这也不是真的.
这是一个有启发性的例子,你以前可能会或可能没有看过的语法:
foo :: Num a => a -> a
这是一个相当标准的函数签名,它接受一个数字并对其执行某些操作并为您提供另一个数字.然而,我在Haskell中所说的"数字"实际上是指一种实现"Num"类的任意类型"a".
因此,这解析为英语:
让a指示实现Num类型类的类型,然后此方法的签名是一个类型为a的参数,以及类型为a的返回值
类似的事情发生在数据上.
在我看来,Cons的规范中的List实例也让你感到困惑:在解析时要非常小心:而Cons指定了一个构造函数,它基本上是Haskell将数据包装进的模式,(列表a)看起来像构造函数,但实际上只是一个类型,如Int或Double.a是一种类型,而不是任何意义上的值.
编辑:响应最近的编辑.
我认为首先需要进行解剖.然后我将逐点处理你的问题.
Haskell数据构造函数有点奇怪,因为您定义了构造函数签名,并且您不必进行任何其他脚手架.Haskell中的数据类型没有任何成员变量的概念.(注意:有一种替代语法,这种思维方式更适合,但现在让我们忽略它).
另一件事是Haskell代码密集; 它的类型签名就像那样.所以期望看到在不同的上下文中重用相同的符号.类型推理在这里也起着重要作用.
所以,回到你的类型:
data List a = Cons a (List a)
| Nil
我把它分成几块:
data List a
这定义了类型的名称,以及稍后将具有的任何参数化类型.请注意,您只会在其他类型的签名中看到此内容.
Cons a (List a) | Nil
这是数据构造函数的名称.这不是一种类型.但是,我们可以对它进行模式匹配,ala:
foo :: List a -> Bool foo Nil = True
注意List a是签名中的类型,Nil既是数据构造函数又是我们模式匹配的"东西".
Cons a (List a)
这些是我们插入构造函数的值的类型.Cons有两个条目,一个是a类型,另一个是List a类型.
所以我的问题是,为什么第一个字段中的"a"表示"此字段的类型为'a',此字段的值为'a'",而第二个字段中的"a"仅表示"类型"这个字段是'列表'"?
简单:不要把它当作我们指定的类型; 想到它有Haskell推断出它的类型.因此,对于我们的意图和目的,我们只是在那里坚持0,在第二部分坚持Nil.然后,Haskell查看我们的代码并思考:
(注意,实际上并不是它的实现方式.在推理类型时,Haskell可以做很多奇怪的事情,这也是错误消息很糟糕的部分原因.)
| 归档时间: |
|
| 查看次数: |
630 次 |
| 最近记录: |