【问题标题】:Why does this expression have a valid type?为什么这个表达式有一个有效的类型?
【发布时间】:2017-09-15 03:54:53
【问题描述】:

ghci 中敲打,我碰巧注意到表达式(*) 1 [1..5] 显然有一个有效的类型。

:t (*) 1 [1..5]
(*) 1 [1..5] :: (Enum t, Num [t], Num t) => [t]

显然它是一个包含多个类型约束的列表,包括Num [t],这在我看来是不可能的,就像它应该给出错误一样。

这是表达式的类型?为什么ghci:t命令在这里没有报错?

【问题讨论】:

    标签: haskell ghci applicative lifting derivingvia


    【解决方案1】:

    Num [t] 不仅可能,而且很简单:

    import Control.Applicative
    liftA0 = pure -- hobgoblins, simple minds, etc.
    liftA1 = fmap
    instance Num t => Num [t] where
        (+) = liftA2 (+)
        (-) = liftA2 (-)
        (*) = liftA2 (*)
        negate = liftA1 negate
        abs    = liftA1 abs
        signum = liftA1 signum
        fromInteger n = liftA0 (fromInteger n)
    

    因此,如果 GHC 产生错误而不是推断您的表达式可以使用适当的实例很好地键入,那将是非常糟糕的。

    当然,用真实代码编写这个实例也很糟糕,但 GHC 不应该像我们人类那样对代码做出判断。

    【讨论】:

    • 可以为 [] 定义一个 Num 实例,使其表现得像多项式(即具有前导 0 的无限序列),这在实际代码中编写起来并不可怕。
    • abssignum 会有点尴尬。
    • @gallais 定义不需要有用,只要保持一致即可。 signum x = [1]abs = id 对于此类实例的大多数预期用途可能就足够了。
    • 在这种情况下也可以是undefined,不是吗?如果你错误地使用这些无意义的函数,至少你会得到一个运行时错误(abssignum 附带法律)
    • @mb14 我认为这样做仍然很糟糕;至少使用newtype 来掩盖你的耻辱。
    【解决方案2】:

    让我们看看这些约束是如何解释类​​型的。

    数字

    在 Haskell 中,文字数字被替换为对 fromInteger 的调用(或 fromRational,如果其中包含小数点或“e”)。这样,一个人可以写 '1' 并将其设置为 float 或 double 或 int 或其他。 fromInteger 的类型是

    fromInteger :: Num a => a
    

    所以1 被脱糖为fromInteger (1::Integer),其类型为Num t => t

    范围

    在 Haskell 中,语法 [a..b] 被转换为调用 enumFromTo a b,类型为 enumFromTo :: Enum a => a -> a -> [a]。把这些放在一起,我们得到

    [1..5] == enumFromTo (fromInteger 1) (fromInteger 5) :: (Enum a, Num a) => [a]
    

    把它们放在一起

    现在(*) 的类型是Num b => b -> b -> b,所以我们将它们组合在一起得到:

    (Num t,
    Num a,
    Enum a,
    Num b,
    t~b,
    [a]~b) => b
    

    注意a~b 表示ab 的类型相同。结合这些给出类型

    (Num a, Enum a, Num [a]) => [a]
    

    【讨论】:

      【解决方案3】:

      这种惯用/应用提升模式以Data.Monoid.Ap 的形式存在,其中Ap [] a 使用purefmapliftA2 指定提升操作:(+) = liftA2 (+)

      >> :set -XDerivingVia
      >> :set -XStandaloneDeriving
      >>
      >> import Data.Monoid (Ap(..))
      >>
      >> deriving via Ap [] a instance Num a => Num [a]
      >>
      >> 1 * [1..5]
      [1,2,3,4,5]
      >> [100,200] * [1..5]
      [100,200,300,400,500,200,400,600,800,1000]
      

      列表的行为是derived via Ap [] a。你通过ZipList得到不同的应用行为

      >> import Control.Applicative (ZipList(..))
      >> 
      >> deriving via Ap ZipList a instance Num a => Num [a]
      >>
      >> 1 * [1..5]
      [1,2,3,4,5]
      >> [100,200] * [1..5]
      [100,400]
      

      【讨论】:

      • 好的,但是你怎么会这样得到Enum
      • Idk 关于列表的Enum 实例,但它可以通过Generically 导出
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-23
      • 1970-01-01
      相关资源
      最近更新 更多