【问题标题】:Since Haskell is lazily evaluated, why doesn't this code work?既然 Haskell 是惰性求值的,为什么这段代码不起作用?
【发布时间】:2018-10-27 21:19:55
【问题描述】:
--defining function
safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x:_) = Just x

--calling function
safeHead (4:5:3:[]:[])

当我使用参数 (4:5:3:[]:[]) 调用 safeHead 时,模式只评估参数以查看它是否为空或是否有头部。因此,尽管有无意义的“[]:[]”,但它不应该抛出错误,因为这部分甚至没有被评估。

【问题讨论】:

  • 因为这是一个类型错误。编译器不是惰性的,并且会执行类型检查。
  • 一开始你不能构造一个有两种不同类型的列表。
  • 以防万一您以前从未使用过具有编译时类型的语言:类型是在程序运行时计算的值上的 not 标记。类型是对哪些值可能出现在某个地方的静态限制。
  • @Redu 4[] 不一定有两种不同的类型。可以有相同的类型。
  • @WillemVanOnsem 这是一个 type 错误吗? :t safeHead (4:5:3:[]:[]) 报告了一个类型,所以可能不是。

标签: haskell lazy-evaluation


【解决方案1】:

它不应该抛出错误,因为这部分甚至没有被评估。

它没有被评估的事实是无关紧要的。 Haskell 是一种静态类型语言,编译器会检查类型。它不必为了对值进行类型检查而计算结果:所有函数的输入和输出类型都是已知的(计算良好),并且 Haskell 编译器会验证一个函数的输出类型与为处理该输出而调用的函数。

类型检查在编译时完成,Haskell 编译器不是惰性的(在某种意义上它在生成二进制文件之前执行这些检查,而不仅仅是在运行之前编码)。编译器急切地检查类型并保证程序从类型系统的角度来看是合理的。

例如以下表达式将进行类型检查:

1 : 2 : undefined

undefined :: a 会在评估时引发错误。

Haskell 允许定义 Numbers 的新类型,因此您可以创建一个类型类,将数字 453 解析为您自己类型的数字。严格来说,该类型可以是一个列表。

但是,如果您对此进行评估,Haskell 会找不到要使用的类型,并且会引发错误:

Prelude> safeHead (4:5:3:[]:[])

<interactive>:6:1: error:
    • Non type-variable argument in the constraint: Num [t]
      (Use FlexibleContexts to permit this)
    • When checking the inferred type
        it :: forall t. Num [t] => Maybe [t]

所以这里它正在寻找列表元素的类型,它发现这些应该是列表,因为 [] 元素(最后一个元素),但同时这些应该是Nums,现在找不到这样的类型,因此出现错误。

严格来说,我们可以构造这样一个类型,这样我们就可以以适当的方式使用函数:

Prelude> data A = A deriving Show
Prelude> :{
Prelude| instance Num [A] where
Prelude|     (+) = undefined
Prelude|     (*) = undefined
Prelude|     abs = undefined
Prelude|     fromInteger _ = [A]
Prelude|     negate = undefined
Prelude|     signum = undefined
Prelude| :}
Prelude> :{
Prelude| safeHead :: [a] -> Maybe a
Prelude| safeHead [] = Nothing
Prelude| safeHead (x:_) = Just x
Prelude| :}
Prelude> safeHead (4:5:3:[]:[]) :: Maybe [A]
Just [A]

【讨论】:

    【解决方案2】:

    这需要一些按摩,但有时您可以让 ghc defer 键入此类错误,并且无论如何都会给您答案。

    -- defer.hs
    safeHead :: [a] -> Maybe a
    safeHead [] = Nothing
    safeHead (x:_) = Just x
    
    n :: Int
    n = []
    
    nonsense :: [Int]
    nonsense = (4 : 5 : 3 : n : [])
    
    r :: Maybe Int
    r = safeHead nonsense
    

    鉴于上述文件,如果您使用 -fdefer-type-errors 标志将其加载到 ghc 或 ghci...

    $ ghci -fdefer-type-errors defer.hs
    
    defer.hs:6:5: warning: [-Wdeferred-type-errors]
        • Couldn't match expected type ‘Int’ with actual type ‘[a0]’
        • In the expression: []
          In an equation for ‘n’: n = []
      |
    6 | n = []
      |     ^^
    
    ghci> r
    Just 4
    
    ghci> nonsense !! 3
    *** Exception: defer.hs:6:5: error:
        • Couldn't match expected type ‘Int’ with actual type ‘[a0]’
        • In the expression: []
          In an equation for ‘n’: n = []
    (deferred type error)
    

    通常,类型错误对于 Haskell 程序来说是致命,从而阻止它们被编译。然而一些这样的错误可以被推迟。这通常不是一个好主意,并且延迟类型错误的功能在一定程度上限制了它可以延迟的内容。

    因此,为了回答您的问题,Haskell 并非旨在让类型错误滑动,即使所述类型错误实际上不会影响任何实时代码路径。但是,如果您愿意,有时可以选择加入这种可疑行为。

    【讨论】:

      【解决方案3】:

      Haskell 程序中的每一位都必须有一个类型。 前期

      (4:5:3:[]:[]) 这个词实际上有一个类型。

      > :t safeHead (4:5:3:[]:[])
      safeHead (4:5:3:[]:[]) :: Num [t] => Maybe [t]
      
      > safeHead (4:5:3:[]:[])
      
      <interactive>:10:1:
          No instance for (Num [t0]) arising from a use of `it'
          In a stmt of an interactive GHCi command: print it
      

      只是缺少实例。

      > instance (Num a) => Num [a] where
          a + b = a
          a - b = a
          a * b = a
          abs a = a
          signum a = a
          fromInteger x = [fromInteger x]
      
      > safeHead (4:5:3:[]:[])
      Just [4]
      it :: Num t => Maybe [t]
      

      数值字面量是多态定义,而不是具体值; 4 实际上读作(fromInteger 4) :: Num a =&gt; a,因此特定的fromInteger 根据其实际具有的特定类型执行其特定工作。这里是

      four :: Num t => Num [t]
      four = fromInteger 4         -- according to our definition, it's
           = [fromInteger 4]
      

      事实证明这毕竟不是一个完整的废话。 :)

      【讨论】:

      • 如果您必须将这样的实例用于列表-y 号码,我建议您改用Data.ANumControl.Applicative.ZipListANum (ZipList [1,2,3]) + 1 == ANum (ZipList [2,3,4])
      • 在所有人都认为它不起作用之后,我只是在寻找可以在这里工作的东西。 :) 我不在乎它是否明智。感谢您的参考,但会调查它。
      • @DanBurton 数字列表在某些基础上似乎有一个 Num 实例是明智的。
      • 我同意“某个基数中的数字列表”可能是一个明智的选择,如果它是新类型的,则有一个 Num 实例。当 OP 写 (4:5:3:[]:[]) 时,我相当肯定他们确实不是意味着它是一个列表列表。与其默默地做一些意想不到的奇怪的事情,不如让它成为一个类型错误。
      • 我认为我不提倡也不建议这样做。如果他们不打算这样做,他们就没有实例并且确实得到了错误。所以一切都很好。对我来说,真正奇怪的事情是推迟类型错误。 :)
      猜你喜欢
      • 1970-01-01
      • 2010-09-18
      相关资源
      最近更新 更多