我是一位C#开发人员,他正在通过"Real World Haskell"来真正地理解函数式编程,因此,当我学习F#时,我会真正使用它,而不仅仅是“用F#编写C#代码”。

好吧,今天我遇到了一个例子,我认为我理解了3次不同的时间,然后才看到我错过的东西,更新了我的解释,然后递归了(也诅咒了,相信我)。

现在,我相信我确实了解它,并且在下面写了详细的“英语解释”。您能请Haskell专家确认您的理解或指出我错过的内容吗?

注意:Haskell代码段(直接从书中引用)定义了一个自定义类型,该类型与内置的Haskell列表类型同构。

Haskell代码段

data List a = Cons a (List a)
              | Nil
              defining Show

编辑:经过一些回应,我看到了一个误解,但是对于可以纠正该错误的Haskell“解析”规则并不太清楚。因此,我在下面包括了我的原始(不正确)解释,然后进行了更正,接着是对我仍然不清楚的问题。

ojit_redit:这是我的原始(不正确)“英语解释”
  • 我正在定义一个名为“列表”的类型。
  • 列表类型已参数化。它具有单个类型参数。
  • 有2个值构造函数可用于创建List的实例。一个值构造函数称为“Nil”,另一个值构造函数称为“Cons”。
  • 如果使用“Nil”值构造函数,则没有字段。
  • “缺点”值构造函数具有单个类型参数。
  • 如果使用“缺点”值构造函数,则必须提供2个字段。第一个必填字段是List的实例。第二个必填字段是a的实例。
  • (我故意省略了有关“定义显示”的任何内容,因为它不属于我现在要关注的内容)。

  • 更正后的解释如下(以粗体显示)
  • 我正在定义一个名为“列表”的类型。
  • 列表类型已参数化。它
    具有单个类型参数。
  • 有2个值构造函数
    可以用来制作实例
    list 。一个值构造函数是
    称为“无”和其他值
    构造函数称为“缺点”。
  • 如果使用“Nil”值构造函数,则没有字段。

    5。(此行已被删除...不正确)“Cons”值构造函数具有单个类型参数。
  • 如果使用“缺点”值构造函数,则有2个字段
    必须提供。首先
    必填字段是的一个实例。
    第二个必填字段是
    “列表列表”的实例。
  • (我故意省略了有关“定义显示”的任何内容,因为它不属于我现在要关注的内容)。

  • 仍然不清楚的问题

    最初的困惑是关于片段中“Cons a(List a)”的部分。实际上,那部分我仍然不清楚。

    人们指出,“缺点”标记后面的每一行都是类型,而不是值。因此,这意味着此行显示“Cons值构造函数具有2个字段:一个字段为'a'类型,而另一个类型为'list-of-a'。

    知道这非常有帮助。但是,仍然不清楚。当我使用Cons值构造函数创建实例时,这些实例“解释”第一个“a”为“将传入的值放在此处”。但是他们对第二个“a”的解释不同。

    例如,考虑以下GHCI session :
    *Main> Cons 0 Nil
    Cons 0 Nil
    *Main> Cons 1 it
    Cons 1 (Cons 0 Nil)
    *Main>
    

    当我键入“Cons 0 Nil”时,它将使用“Cons”值构造函数创建List的实例。从0开始,它得知type参数是“Integer”。到目前为止,没有困惑。

    但是,它还确定Cons的第一个字段的值为0。然而,它对第二个字段的值没有任何决定...它仅确定第二个字段的类型为“列表整数”。

    所以我的问题是,为什么第一个字段中的“a”表示“此字段的类型为'a',而该字段的值为'a'”,而第二个字段中的“a”仅表示“该类型此字段的名称为“列表a””?

    编辑:我相信我已经看到了曙光,这要归功于一些回应。我在这里说清楚。 (如果仍然以某种方式仍然不正确,请务必告知我!)

    在片段“Cons a(List a)”中,我们说“Cons”值构造函数具有两个字段,并且第一个字段的类型为“a”,第二个字段的类型为“List of a” '。

    这就是我们要说的! 特别是,我们对值(value)一无所知!这是我所缺少的关键点。

    稍后,我们要使用“Cons”值构造函数创建一个实例。我们在解释器中输入:“Cons 0 Nil”。此显式告诉Cons值构造函数将0用作第一字段的值,并将Nil用作第二字段的值。

    这就是全部。一旦知道值构造函数定义只指定类型,则一切都变得清晰。

    谢谢大家的帮助。正如我所说,如果仍然有问题,请务必告诉我。谢谢。

    最佳答案



    不,当您声明data List a时,您已经对其进行了参数化。一个有效的属性是,如果我有一个Nil::List Int,则无法将其与Nil::List Char互换。



    您已将其交换:第一个必填字段是a的实例,第二个字段是List的实例。

    This chapter of Real World Haskell可能很有趣。



    不。一旦在类型中声明了一个参数,就可以重用它,否则说“应该在该类型中使用该类型”。有点像a -> b -> a类型签名:a正在参数化类型,但随后我必须使用相同的a作为返回值。



    不,那是不正确的。这只是意味着数据类型将参数化为某种类型a。



    不,那也不是真的。

    这是一个说明性示例,您之前可能已经或可能没有看到过其语法:

    foo::Num a => a-> a

    这是一个相当标准的函数签名,该函数接受一个数字并对其执行某些操作并为您提供另一个数字。我实际上在Haskell所说的“数字”是指实现“Num”类的任意类型“a”。

    因此,这解析为英语:



    数据也会发生类似的情况。

    我还发现Cons规范中的List实例也使您感到困惑:解析时要特别小心:而Cons正在指定构造函数,这基本上是Haskell将数据包装到其中的一种模式,(列表a)看起来像一个构造函数,但实际上只是一个类型,例如Int或Double。 a是类型,而不是任何意义上的值。

    编辑:响应最新的编辑。

    我认为首先需要解剖。然后,我将逐点处理您的问题。

    Haskell数据构造函数有点怪异,因为您定义了构造函数签名,而不必进行任何其他搭建。 Haskell中的数据类型没有成员变量的任何概念。 (请注意:还有另一种语法更适合这种思维方式,但现在暂时忽略它)。

    另一件事是Haskell代码很密集。它的类型签名就是这样。因此,期望看到相同的符号在不同的上下文中被重用。类型推断在这里也起着重要作用。

    所以,回到您的类型:

    数据列表a =缺点a(列表a)
    |零

    我将其分成几部分:

    数据 list 一

    这定义了类型的名称,以及以后将具有的任何参数化类型。请注意,您只会在其他类型签名中看到它。

    缺点(列表a)|


    这是数据构造函数的名称。这不是一种类型。但是,我们可以为其进行模式匹配,ala:

    foo::列出一个-> Bool
    foo Nil =真

    注意List a是签名中的类型,而Nil既是数据构造函数,也是我们进行模式匹配的“事物”。

    缺点a( list a)

    这些是我们插入到构造函数中的值的类型。 Cons有两个条目,一个条目是a类型,一个条目是List a类型。



    简单:不要在指定类型时考虑它;想起Haskell正在推断出它的类型。因此,出于我们的意图和目的,我们只是在其中添加0,在第二部分添加Nil。然后,Haskell查看我们的代码并认为:

  • 嗯,我想知道Cons 0 Nil的类型是
  • 好吧,Cons是List a的数据构造函数。我想知道List a的类型是
  • 好吧,第一个参数中使用了a,因此,由于第一个参数是一个Int(另一个简化; 0实际上是一个奇怪的东西,类型分类为Num),所以这意味着a是一个Num
  • 嘿,好吧,这也意味着Nil的类型是List Int,尽管那里实际上没有什么可以说

  • (请注意,这实际上并不是它的实现方式。Haskell在推断类型时可以做很多奇怪的事情,这就是错误消息很烂的部分原因。)

    关于c# - 请确认或更正此Haskell代码段的 “English interpretation”,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/775730/

    10-13 07:46
    查看更多