【问题标题】:Understand and use the basic concept of Haskell's Integral typeclass理解和使用 Haskell 的 Integral 类型类的基本概念
【发布时间】:2015-11-04 15:04:35
【问题描述】:

我正在学习一门侧重于 Haskell 和 Prolog 的课程,并且我正在学习一个即将到来的测试。

我们得到了一个签名:

myList
  :: (Integral a)
  => [a]

我们必须创建一个变量myList,它将返回一个与标准正整数列表不同的无限列表,方法是通过将第三个元素从第一个元素开始向右移动两个位置来改变它的位置。

例如,开头看起来像: 2,3,1,5,6,4,8,9,7.. 在包含正面元素的标准列表中。

我试图用这样的代码解决这个问题:

myList (x:y:z:xs)
  = y:z:x:(myList(xs))
myList [] = []
myList [x] = [x]

它给出了所需的结果,但它没有遵循签名。有人可以解释如何解决它以使其适合签名以及为什么会这样。

谢谢。

【问题讨论】:

  • “给出所需的结果”——你如何获得它?
  • 提示:Integral 不是类型,你不必理解它来解决这个任务。暂时忽略它。
  • 嗯,我确实必须根据签名得到它,我不知道如何才能得到它。我当前的 :t 给出 [a] -> [a]
  • @n.m.我把它叫出来测试:myList (take 100 [1,2..])
  • 您编写的代码是一个改变现有列表的函数。要求似乎是生成一个列表,而不是修改现有列表。

标签: haskell types typeclass


【解决方案1】:

函数(事实上,你完全正确的实现)

myList (x:y:z:xs) = y:z:x:(myList xs)
myList []         = []
myList [x]        = [x]

足够通用,不依赖于列表中元素的类型为Integral a => a。所以如果你让 Haskell 推断它的类型,它会推断出[a] -> [a]。如果您将类型限制为Integral a => [a] -> [a],它仍然可以工作,但不那么通用,这会将使用限制为整数类型。


下面是原理的演示:

Prelude> :{
Prelude| let myList (x:y:z:xs) = y:z:x:(myList(xs))
Prelude|     myList [] = []
Prelude|     myList [x] = [x]
Prelude| :}
Prelude> :t myList
myList :: [a] -> [a]

Prelude> take 15 $ myList ['a'..]
"bcaefdhigkljnom"
Prelude> take 15 $ myList [1..]
[2,3,1,5,6,4,8,9,7,11,12,10,14,15,13]

但是

Prelude> :{
Prelude| let myList :: Integral a => [a] -> [a]
Prelude|     myList (x:y:z:xs) = y:z:x:(myList(xs))
Prelude|     myList [] = []
Prelude|     myList [x] = [x]
Prelude| :}
Prelude> :t myList
myList :: Integral a => [a] -> [a]

Prelude> take 15 $ myList [1..]
[2,3,1,5,6,4,8,9,7,11,12,10,14,15,13]

Prelude> take 15 $ myList ['a'..]    
<interactive>:34:11:
    No instance for (Integral Char) arising from a use of ‘myList’
    In the second argument of ‘($)’, namely ‘myList ['a' .. ]’
    In the expression: take 15 $ myList ['a' .. ]
    In an equation for ‘it’: it = take 15 $ myList ['a' .. ]

所以重点是,两个定义是等价的,并且能够做同样的事情,但是约束类型签名(我会说不合理)不如其他定义有用具有通用类型签名的那个。

如果分配需要Integral a =&gt; [a] -&gt; [a] 类型的函数,您真正需要做的只是用该类型签名准确地注释您已经拥有的函数。但是,没有(合理/合理的)方法可以以某种方式引导 Haskell 从函数定义中推断出该类型,因为这将需要以某种方式间接指示列表必须包含支持 Integral 中的操作的类型的值。 ..亚达亚达。

最后一点:您的实现/算法完全正确,但在类型签名和通用性概念方面存在不足。


编辑:如果你真正需要的不是一个函数而是一个列表(你的问题在这方面有点模棱两可),你需要做的就是重命名myList的以下定义例如myList'go(我认为这是嵌套递归助手的典型名称)或其他东西(可以但不必隐藏在列表 myList 中),然后将 [1..] 传递给它,将结果分配给myList

myList :: Integral a => [a]
myList = go [1..]
  where go (x:y:z:xs) = y:z:x:(go xs)
        go []         = []
        go [x]        = [x]

当然是这样看的,Integral a =&gt; [a] 确实是列表的一个非常通用的签名(但不是最通用的,这将是 (Enum a, Num a) =&gt; [a],正如我被 dfeuer 的评论所引导的那样),因为a 的类型不能由传递给函数的输入类型来确定,因为你总是在传递[1..]

【讨论】:

  • "如果赋值需要一个 Integral a => [a] -> [a] 类型的函数,"虽然赋值要求 Integral a =&gt; [a] - 不是函数。
  • 感谢您的解释和回答:)
  • 只是一个愚蠢的后续问题。如果我在 .hs 文件中设置类型:myList :: Integral a => [a] -> [a],则会出现错误。这是为了说明还是我做错了什么。
  • 歧义出现'null':它可以引用'Data.List.null'或'Data.Set.null'
  • 不导入Data.Set
【解决方案2】:

如果我们想用正确的签名给出答案,而无需手动提供任何签名,我们必须查看 Integral 类并查看它实现了哪些方法。应用任何此类方法都可能会强制执行正确的签名。

Prelude> :i Integral
class (Real a, Enum a) => Integral a where
  quot :: a -> a -> a
  rem :: a -> a -> a
  div :: a -> a -> a
  mod :: a -> a -> a
  quotRem :: a -> a -> (a, a)
  divMod :: a -> a -> (a, a)
  toInteger :: a -> Integer

由于我们的序列与除以 3 的余数有关,divmod 看起来很有希望。

在对算术进行了一些摆弄之后,我们得到了类似的东西

Prelude> let myList = map (\x -> x - x `mod` 3 + (x+2) `mod` 3 + 1) [0..]
Prelude> :t myList
myList :: Integral b => [b]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-28
    • 2012-07-07
    相关资源
    最近更新 更多