【问题标题】:How to use custom version of List: `data List a = Nil | Cons a (List a)`?如何使用自定义版本的 List:`data List a = Nil |缺点a(列表a)`?
【发布时间】:2023-03-12 15:27:02
【问题描述】:

此问题基于 Graham Hutton 第二版“Haskell 编程”一书“声明类型和类”一章中的示例.

数据声明为: data List a = Nil | Cons a (List a)

使用此声明的示例函数是:

len :: List a -> Int
len Nil = 0
len (Cons _ xs) = 1 + len xs

但是无论我怎么尝试,我似乎都无法使用len的功能:

  • len Cons 1 Cons 2 Cons 3
  • len 1
  • len Cons
  • len (1)
  • len [1,2]
  • len Cons [1]
  • len (1,2)
  • len Cons (1,2)
  • len Cons 1 2
  • len (Cons (1,2))
  • len (Cons 1 2)

我错过了lenCons 的任何排列吗?还是这个例子根本行不通?

【问题讨论】:

  • 首先尝试实现foo :: List Int 类型的表达式可能更简单。列表是递归数据类型,所以起初看如何“完成”递归可能有点棘手。但是有一种方法可以构建一个不涉及使用另一个列表的列表。
  • @AsadSaeeduddin:谢谢,我终于可以和len (Cons 2 (Cons 2 Nil)) 一起工作了。看起来 Haskell 自我声明的函数通常会导致相当长的术语。
  • 你可以声明一个中缀构造函数。它必须以: 开头。例如。 :.。使用适当的固定性声明,您可以拥有len $ 1 :. 2 :. 3 :. Nil

标签: list haskell types syntax custom-data-type


【解决方案1】:

您传递给len 的参数不是Lists。列表的末尾有Nil,因此列表的形式为NilCons … NilCons … (Cons … Nil)Cons … (Cons … (Cons … Nil)) 等。所以最终对于每个列表,列表的末尾都标有@987654330 @。 Nil 等同于 Haskell 的 [a] 类型的 []

此外,例如,您不能传递len Cons 1 Nil,此后它将被解释为((len Cons) 1) Nil。参数应该是一个列表。通过使用括号,您可以将其写为len (Cons 1 Nil)

对于给定的示例数据,您可以将其重写为:

  • len Cons 1 Cons 2 Cons 3len (Cons 1 (Cons 2 (Cons 3 Nil)))
  • len 1len (Cons 1 Nil)
  • len Conslen Nil
  • len (1)len (Cons 1 Nil)
  • len [1,2]len (Cons 1 (Cons 2 Nil))
  • len Cons [1]len (Cons 1 Nil)
  • len (1,2)len (Cons 1 (Cons 2 Nil))
  • len Cons (1,2)len (Cons 1 (Cons 2 Nil))
  • len Cons 1 2len (Cons 1 (Cons 2 Nil))
  • len (Cons (1,2))len (Cons 1 (Cons 2 Nil))
  • len (Cons 1 2)len (Cons 1 (Cons 2 Nil))

您也可以使用OverloadedLists [haskell-doc] 扩展名来使用列表语法。在这种情况下,您需要实现IsList 类型类:

{-# LANGUAGE TypeFamilies #-}

import GHC.Exts(IsList(..))

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

instance IsList (List a) where
    type Item (List a) = a
    toList Nil = []
    toList (Cons x xs) = x : toList xs
    fromList [] = Nil
    fromList (x:xs) = Cons x (fromList xs)

如果您随后启用 OverloadedLists 扩展,您可以将这些写为列表文字:

{-# LANGUAGE OverloadedLists #-}

-- …

main = print (len [1,2])

【讨论】:

    【解决方案2】:

    您完全按照定义使用函数:

    len  Nil         = 0
    len (Cons _x xs) = 1 + len xs
    

    那么,

    list1 = Nil           -- matches the first pattern
    list2 = Cons 2 list1  -- matches the second pattern
    list3 = Cons 3 list2  -- matches the second pattern
    list4 = Cons 4 list3  -- matches the second pattern
    

    等等等等。

    手工写出这样的示例数据,我们需要使用括号正确分组子术语以重新创建有效术语,例如

    list5 = Cons 5 list4
          = Cons 5 (Cons 4 list3)
          = Cons 5 (Cons 4 (Cons 3 list2))
          = ...
          = Cons 5 (Cons 4 (Cons 3 (Cons 2 Nil)))
    

    这一切甚至都没有查看数据类型定义。

    当然可以使用任何东西来代替12 等,只要它们都是相同的类型,例如以下也是一个有效的术语:

    list54 = Cons list5 (Cons list4 Nil)
    

    为什么?由于数据类型的定义,

    data List a = Nil           --  `Nil` constructs (is) a valid `List a` type term,
                |               -- OR,
                  Cons          --  `Cons x xs` constructs (is) a valid `List a` type term,  IF 
                       a        --       `x` is a valid term of type `a`                  ,  AND
                      (List a)  --       `xs` is a valid term of type `List a`
    

    Nil 构造(是)一个有效的List a 类型术语,而Cons x xs 构造(是)一个有效的List a 类型术语,如果 x 是一个有效的术语键入a xsList a 类型的有效术语,其中a 是一回事

    例如根据List a 数据类型定义,Cons 1 (Cons list2 Nil) 不是有效术语,尽管函数 len 似乎也可以处理它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-09-18
      • 1970-01-01
      • 2021-07-06
      • 2012-12-05
      • 2015-06-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多