我是一个正在努力的开发人员 “现实世界哈斯克尔” 为了真正理解功能编程,因此,当我学习F#时,我会真正掌握它,而不仅仅是“在f#中写C#代码”,可以这么说。

好吧,今天我遇到了一个例子,我以为我理解了三个不同的时间,然后看到了我错过的东西,更新我的解释并重复(也诅咒,也相信我)。

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

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

Haskell代码片段

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

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

编辑:这是我的原始(不正确)摘要的“英语解释”

  1. 我正在定义一种称为“列表”的类型。
  2. 列表类型已被参数化。它具有单一类型参数。
  3. 有2个值构造函数可用于制作列表实例。一个值构造函数称为“ nil”,另一个值构造函数称为“ cons”。
  4. 如果您使用“ nil”值构造函数,则没有字段。
  5. “ cons”值构造函数具有单一类型参数。
  6. 如果使用“ Cons”值构造函数,则必须提供2个字段。第一个所需字段是列表的实例。第二个所需字段是一个实例。
  7. (我故意省略了有关“定义表演”的任何内容,因为它不是我现在要关注的一部分)。

校正的解释将如下(粗体更改)

  1. 我正在定义一种称为“列表”的类型。
  2. 列表类型已被参数化。它具有单一类型参数。
  3. 有2个值构造函数可用于制作列表实例。一个值构造函数称为“ nil”,另一个值构造函数称为“ cons”。
  4. 如果您使用“ nil”值构造函数,则没有字段。

    5.(此行已删除...不准确)“ Cons”值构造函数具有单一类型参数。

  5. 如果使用“ Cons”值构造函数,则必须提供2个字段。第一个所需字段是一个实例。第二个所需字段是“ A列表”的实例。

  6. (我故意省略了有关“定义表演”的任何内容,因为它不是我现在要关注的一部分)。

仍然不清楚的问题

最初的混乱是关于摘要的部分,上面写着“ cons a(列表a)”。实际上,这是我仍然不清楚的部分。

人们指出,“弊端”令牌后行上的每个项目都是 类型, ,不是价值。因此,这意味着该行说:“ CONS值构造函数具有2个字段:一个类型A',另一个类型为“ A”'。”

知道这很有帮助。但是,仍然不清楚。当我使用Cons值构造函数创建实例时,这些实例将第一个'a解释为“将值传递给此处”的含义。但是他们做到了 不是 以相同的方式解释第二个“ a”。

例如,考虑此GHCI会话:

*Main> Cons 0 Nil
Cons 0 Nil
*Main> Cons 1 it
Cons 1 (Cons 0 Nil)
*Main> 

当我键入“ cons 0 nil”时,它使用“ cons”值构造函数来创建列表的实例。从0开始,它了解到类型参数为“整数”。到目前为止,没有混乱。

但是,它 确定 价值 缺点的第一个字段是0。但是它决定了 没有什么 有关 价值 第二个字段...仅确定第二个字段有一个 类型 “列表整数”。

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

编辑:我相信我现在已经看到了光,这要归功于几个回应。让我在这里表达它。 (如果以某种方式 仍然 以某种方式不正确,请一定要让我知道!)

在摘要中,我们说“ cons”值构造函数有两个字段,并且第一个字段是类型'a'的字段,第二个字段是类型的'列表' '。

这就是我们所说的! 特别是我们说 没有什么 关于价值!这是我缺少的关键点。

稍后,我们想使用“ cons”值构造函数创建一个实例。我们将其键入解释器:“ Cons 0 nil”。这个 明确 告诉CONS值构造函数将0用于第一个字段的值,并将NIL用作第二个字段的值。

这就是其中的全部。一旦您知道值构造函数定义 除了类型, ,一切都变得清晰。

感谢大家的有益回复。正如我所说,如果还有什么问题,请一定要告诉我。谢谢。

有帮助吗?

解决方案

  • “ cons”值构造函数具有单一类型参数。

不:您已经在声明时已经对其进行了参数 data List a. 。其中一个有效的属性是,如果我有一个nil :: list int,我将无法与nil :: list char互换。

  • 如果使用“ Cons”值构造函数,则必须提供2个字段。第一个所需字段是列表的实例。第二个所需字段是一个实例。

您已经交换了:第一个必需的字段是A的实例,第二个字段是列表的实例。

现实世界的这一章Haskell 可能是感兴趣的。

谢谢。那是我现在正在撰写的一章。因此,当代码上说“ Cons a(列表a)”时,我认为其中的“ Cons A”一部分说明了Cons值构造函数已被参数化。他们尚未涵盖参数化类型的语法,因此我猜想该语法必须需要重新陈述“ A”,如果您打算使用A。但是您是说这不是必需的吗?因此,这不是“ A”的含义?

没有。一旦我们在类型中声明一个参数,我们就可以重复使用它,否则说“应该在那里使用该类型”。有点像 a -> b -> a 类型签名:A是参数化类型,但是我必须使用与返回值相同的A。

好的,但这令人困惑。看来第一个“ a”表示“第一个字段是A的实例”,

不,就是 不是 真的。这只是意味着数据类型在某些类型A上进行了参数。

这也意味着“第一个字段的值与它们传递的值相同”。换句话说,它指定类型和价值。

不,这也不是真的。

这是一个有启发性的例子,您可能以前可能看过的语法:

foo :: Num a => a -> a

这是一个相当标准的签名,对于一个数字并为其做点事并为您提供另一个数字的函数。不过,我实际上是在haskell说的“一个数字”的含义,是某种任意类型的“ a”,它实现了“ num”类。

因此,这解析了英语:

令A表示实现NUM Typeclass的类型,然后该方法的签名是一个具有A类型A的参数,而类型A的返回值

数据发生了类似的事情。

在我看来,Cons规范中列表的实例也使您感到困惑:解析时要非常小心:虽然Cons是指定构造函数,这基本上是Haskell将数据包含在中的模式((列表a)看起来像构造函数,但实际上只是一种类型,例如int或double。 A是一种类型,而不是任何术语的价值。

编辑: 响应最新编辑。

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

Haskell数据构造函数有些怪异,因为您定义了构造函数签名,并且您不必进行其他任何脚手架。 Haskell中的数据类型没有任何成员变量的概念。 (注意:有一种替代语法,这种思维方式更适合,但现在让我们忽略它)。

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

因此,回到您的类型:

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

我将其分为几个:

data 列出

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

缺点 a (List a) |

这是数据构造函数的名称。 这不是类型. 。但是,我们可以将其模式匹配,Ala:

foo :: List a -> Bool
foo Nil = True

请注意,列表A是签名中的类型,而nil既是数据构造函数,又是我们模式匹配的“事物”。

Cons (列表a)

这些是我们插入构造函数的值的类型。缺点有两个条目,一个是A型,一个是类型列表a。

因此,我的问题是,为什么在第一个字段中“ A”表示“该字段的类型为'a”,该字段的值为'a'”,而第二个字段中的“ A”仅表示“类型”这个领域是“''''?

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

  • 嗯,我想知道0个nil是什么类型
  • 好吧,缺点是列表a的数据构造函数。我想知道列表的类型是什么
  • 好吧,A在第一个参数中使用,因此,由于第一个参数是int(另一个简化; 0实际上是一个奇怪的东西,被类型为num),所以这就是eange a是num
  • 嘿,嗯,这也意味着零的类型是int int,即使那里没有任何东西可以说

(请注意,这实际上并不是实现的方式。Haskell可以在推断类型的同时做很多奇怪的事情,这部分是为什么错误消息糟透了的原因。)

其他提示

类比通常在各种方面都缺乏,但是由于您知道C#,所以我认为这可能会有所帮助。

这就是我描述的 List a 在C#中的定义,也许这清除了一些事情(或更有可能使您更加困惑)。

class List<A>
{
}

class Nil<A> : List<A>
{
    public Nil() {}
}

class Cons<A> : List<A>
{
    public A Head;
    public List<A> Tail;

    public Cons(A head, List<A> tail)
    {
        this.Head = head;
        this.Tail = tail;
    }
}

如你看到的;

  • List 类型有一个类型参数(<A>),
  • Nil 构造函数没有任何参数,
  • Cons 构造函数有两个参数,一个值 head 类型 A 和一个价值 tail 类型 List<A>.

现在,在哈斯克尔 NilCons 只是构造函数 List a 数据类型,在C#中它们本身也是类型,因此类比失败。

但我希望这给您一些直观的感觉 A代表。

(并且请评论这种可怕的比较如何与Haskell的数据类型伸张正义。)

Cons a (List a)

缺点的第一个字段是类型的值a“。第二个是类型的值”List a”,即具有与当前列表的参数相同类型的参数化列表。

5是错误的,我会说6,替换两者:

cons {1} a {2}(列表a){3}是一个构造函数,称为cons({1}之前的{1}),对于类型列表a(数据列表a part)需要两个值的值:一个类型A类型({1}和{2})和类型列表a({2}和{3}之间的零件)之间的零件。

为了帮助您获得明显的混乱来源:在Haskell中,您几乎不必给出显式类型的参数 - 键入推断从您的值中剥离类型。因此,从某种意义上说,是的,当您将值传递给函数或构造函数时,您还指定类型,即。传递值的类型。

是的,数据语法有点令人困惑,因为它是双关语和类型,并且并没有真正制作 句法 它们之间的区别。特别是,在构造函数的定义中,类似:

Cons a (List a)

第一个单词是构造函数的名称;每个其他单词都是某些预定类型的名称。这两个 aList a 已经处于范围( a 被带入 a 在 ”data List a”),您是说这些是参数的类型。通过使用同一件事,可以更好地证明它们的角色 记录语法:

Cons { headL :: a, tailL :: List a }

即类型的值 List Int, 如果 它是用 Cons 构造函数,有两个字段: IntList Int. 。如果它是用 Nil, ,它没有字段。

当我键入”Cons 0 Nil“,它使用”Cons“要创建列表实例的值构造函数。从0开始,它知道类型参数为”Integer“到目前为止,没有混乱。

但是,它还确定了缺点的第一个字段的值为0。但它对第二个字段的值一无所知……它仅确定第二个字段具有“列表整数”的类型。

不,它确定第二字段的值为 Nil. 。考虑到您的定义, Nil 是类型的值 List a. 。因此是如此 Cons 0 Nil. 。并在 Cons 1 it 第二个字段的值是 it; IE, Cons 0 Nil. 。这正是replect所显示的:Cons 1 (Cons 0 Nil).

我看了您编辑的问题。

当我使用CONS值构造函数创建实例时,这些实例将第一个'a解释为“含义”,将值放在此处。

在“ cons a(列表a)”中,“ a”和“ list a”都是类型。我不明白“价值”对此有什么影响。

当我键入“ cons 0 nil”时,它使用“ cons”值构造函数来创建列表的实例。从0开始,它了解到类型参数为“整数”。到目前为止,没有混乱。

但是,它还确定了缺点的第一个字段的值为0。但它对第二个字段的值一无所知……它仅确定第二个字段具有“列表整数”的类型。

第二字段的值是 Nil.

因此,我的问题是,为什么在第一个字段中“ A”表示“该字段的类型为'a”,该字段的值为'a'”,而第二个字段中的“ A”仅表示“类型”这个领域是“''''?

第一个字段中的“ a”表示“此字段的类型为'a'”。第二个字段中的“列表”表示“此字段的类型为'列表a'”。在上面的“ cons 0 nil”的情况下,'a'被推断为“整数”。因此,“ cons a(列表a)”变为“ cons Integer(list Integer)”。 0是类型整数的值。零是类型“列表整数”的值。

该领域的价值是“ a”

我不明白你的意思。 'a'是类型变量;它与价值有什么关系?

为了给您一些额外的“帮助”,以防您仍在观看此线程。哈斯克尔(Haskell)有几次惯例,使他人对应该如何做的事情的想法弄乱了 - 在哈斯克尔(Haskell)中,参数化的类型被普遍接受,以至于通常被认为是一种类型级功能。同样,值构造函数也被认为是“特殊”函数,除了“占据(或更多)并因此产生值”之外,还允许模式匹配。

Haskell的另一个“有趣”特征是,它不会明确(或隐式)评估该函数的参数, 即使该论点在括号中. 。让我略有不同:Haskell函数在其他参数之前不会评估括号中的论点。论点仅用于分组目的,而不是将其评估为“第一”。 Haskell将参数分配给(“应用”)功能比任何其他操作都高 - 甚至高于其自己的参数之一的隐式函数应用程序。这就是为什么 Cons 构造者在第二个论点上有帕伦斯, (List a) - 告诉编译器 Cons 有两个论点,没有 . 。括号仅用于分组,而不是优先!

作为一个附带主题,请小心F#中的类型。由于f#根源在ML中,因此其参数化类型在前面的参数 - int list, , 不是 (List Int) 在后面! Haskell以另一种方式做到了这一点,因为这是Haskell执行功能的方式 - 首先是函数,然后对函数进行参数。这鼓励了一种共同的使用模式,并解释了为什么Haskell类型和价值构造函数被大写 - 提醒您您正在处理类型/班级相关的事物。

好吧,我完成了;感谢您让我在您的财产上放这条巨大的墙。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top