请确认或更正我对此Haskell代码段的"英语解释"

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"解析"规则会纠正这个错误.所以我在下面列出了我原来的(不正确的)解释,然后进行了修正,接着是我仍然不清楚的问题.

编辑:这是我原来的(不正确的)"片段的"英语解释"

  1. 我正在定义一个名为"List"的类型.
  2. List类型已参数化.它有一个类型参数.
  3. 有两个值构造函数可用于创建List的实例.一个值构造函数称为"Nil",另一个值构造函数称为"Cons".
  4. 如果使用"Nil"值构造函数,则没有字段.
  5. "Cons"值构造函数具有单个类型参数.
  6. 如果使用"Cons"值构造函数,则必须提供2个字段.第一个必填字段是List的实例.第二个必填字段是a的实例.
  7. (我故意省略了关于"定义Show"的任何内容,因为它不是我现在想要关注的部分).

更正的解释如下(BOLD的变化)

  1. 我正在定义一个名为"List"的类型.
  2. List类型已参数化.它有一个类型参数.
  3. 有两个值构造函数可用于创建List的实例.一个值构造函数称为"Nil",另一个值构造函数称为"Cons".
  4. 如果使用"Nil"值构造函数,则没有字段.

    5.(此行已被删除...它不准确)"Cons"值构造函数具有单个类型参数.

  5. 如果使用"Cons"值构造函数,则必须提供2个字段.第一个必填字段是a的实例.第二个必填字段是"List-of-a"的实例.

  6. (我故意省略了关于"定义Show"的任何内容,因为它不是我现在想要关注的部分).

这个问题目前还不清楚

最初的混淆是关于片段的部分"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作为第二个字段的值.

这就是它的全部内容.一旦你知道值构造函数定义只指定类型,一切都变得清晰.

感谢大家的有益回应.正如我所说,如果还有任何问题,请务必告诉我.谢谢.

Edw*_*ang 8

  • "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查看我们的代码并思考:

  • 嗯,我想知道Cons 0 Nil的类型是什么
  • 好吧,Cons是List a的数据构造函数.我想知道列表a的类型是什么
  • 好吧,a在第一个参数中使用,所以因为第一个参数是Int(另一个简化; 0实际上是一个奇怪的东西,被称为Num),所以这意味着a是一个Num
  • 嘿,嗯,这也意味着Nil的类型是List Int,即使那里没有任何实际可以说的

(注意,实际上并不是它的实现方式.在推理类型时,Haskell可以做很多奇怪的事情,这也是错误消息很糟糕的部分原因.)