【问题标题】:Type Inference in Haskell v. ScalaHaskell v. Scala 中的类型推断
【发布时间】:2015-04-29 17:28:20
【问题描述】:

给定以下代码:

Prelude> let f x = if (x) then 55 else "foo"

为什么编译器会寻找Num [Char]

<interactive>:2:23:
    No instance for (Num [Char]) arising from the literal `55'
    In the expression: 55
    In the expression: if (x) then 55 else "foo"
    In an equation for `f': f x = if (x) then 55 else "foo"

但是,在 Scala 中,它会找到55"foo" 的最小上限,即Any

scala> def f(x: Boolean) = if (x) 55 else "foo"
f: (x: Boolean)Any

import scala.reflect.runtime.universe._

scala> lub( List[Type]( typeOf[Int], typeOf[String] ) )
res4: reflect.runtime.universe.Type = Any

Haskell 和 Scala 的类型推断之间的主要区别是什么?

【问题讨论】:

  • 主要区别在于 Scala 有子类型,而 Haskell 没有。一个不太重要的区别可能是 Scala 中的数字不是多态的,尽管我还不足以肯定 Scala 的专家。
  • 编译器查找Num [Char],因为"foo"是一个字符串,即[Char],并且出现在55的另一个分支中。这意味着55 也必须是[Char] 类型,并且搜索了一种将数字55 解释为字符串的方法(所述实例)。

标签: scala haskell type-inference


【解决方案1】:

您可以在 Haskell 中为 Num [Char] 添加一个实例,如果这是您想要的:

{-# LANGUAGE FlexibleInstances #-}

import Data.Function (on)
import Data.Composition ((.:))  -- cabal install composition

helper :: (Integer -> Integer -> Integer) -> (String -> String -> String)
helper op = show .: on op read

cast :: (Integer -> Integer) -> (String -> String)
cast f = show . f . read

instance Num [Char] where
    fromInteger = show
    (+) = helper (+)
    (-) = helper (-)
    (*) = helper (*)
    abs = cast abs
    signum = cast signum
    negate = cast negate

这只是一种可能的实现方式。这将使您的示例编译:

> let f x = if x then 55 else "foo"
> f True
"55"
> f False
"foo"

Haskell 具有多态数字文字,因此 55 :: Num a =&gt; a,并且由于 if 的两个分支必须返回相同的类型,因此您通过让 else 分支返回 "foo" 来强制 a ~ [Char]。这会导致一些令人困惑的错误消息,但它可能是一个非常强大的功能。这意味着任何数字文字都可以充当您需要的类型,这与 OverloadedStrings 扩展背后的概念相同,允许您使用多态字符串文字而不是在任何需要 Text 的地方使用 packByteString.

Scala 使用子类型化并且对所有值都有一个泛型类型。它允许您放松函数的类型安全性并按字面意思返回 Anything。 Haskell 根本没有子类型,因此统一这些类型(类似于查找 LUB)的唯一方法是使用数字文字在 Num 约束下是多态的这一事实,因此为了编译 "foo" 必须实施Num。实际上,如果你启用了OverloadedStrings f 将与类型一起编译就好了

f :: (Data.String.IsString a, Num a) => Bool -> a

默认情况下没有任何类型同时满足这两个约束,但 GHC 很乐意将其视为有效函数。

【讨论】:

    【解决方案2】:

    这是因为在 Haskell 中数字文字是如何解释的。

    λ> :type 412
    412 :: Num a => a
    

    这在幕后所做的是调用NumfromInteger 函数将412 或在您的情况下为55 转换为任何实例。

    我们可以这样看Num类:

    λ :info Num
    class Num a where
      (+) :: a -> a -> a
      (*) :: a -> a -> a
      (-) :: a -> a -> a
      negate :: a -> a
      abs :: a -> a
      signum :: a -> a
      fromInteger :: Integer -> a
    

    因此,当您编写55"foo" 时,Haskell 首先意识到返回值必须是[Char] 类型,因为"foo" :: [Char],并尝试找到与之匹配的Num 实例。当然失败了。

    Scala 是一种非常不严格类型的语言,并且因为它没有与 Haskell 完全一样的类型类,所以只能求助于Any 来描述这样的类型。我认为我不必解释为什么这不好,因为类型错误从来都不是一件有趣的事情。


    如果您希望能够返回IntString,您可以使用Either

    f :: Bool -> Either Int String
    f b = if b then Left 55 else Right "foo"
    

    【讨论】:

      【解决方案3】:

      如果您仍然有些困惑,了解一下后端的工作原理会有所帮助。

      在 Haskell 中,类型类是与类型关联的函数的字典。对于每个类型类,每种类型只能有一个函数字典。当它满足时,我们说它满足相关的约束。因此,如果您在 GHCi 中键入 :t 5,它会告诉您 5 的类型是 Num a =&gt; a,换句话说,有许多类型的值是 5,而这些类型的值是 Num字典。这是真的,因为Num 字典定义了fromInteger :: Num a =&gt; Integer -&gt; a 函数,该函数采用Integer 类型的特定5 并将其转换为其他类型。

      所以它抱怨它没有为 String 类型定义的 Num 字典——当然它没有,因为人们通常不使用字符串作为数字. (String 类型是 [Char] 的别名。)

      所以当你写的时候:

      let f x = if x then 55 else "foo"
      

      接下来会发生什么:

      1. Haskell 是强类型的,if 是一个具有具体类型的具体返回值的表达式。两个分支必须具有相同的类型。
      2. GHC 知道并首先辨别x 的类型是x :: Bool(因为它显示为对 if 分支的测试,而 Haskell 的强类型不允许非显式强制)并且55 :: Num x =&gt; x"foo" :: String
      3. GHC 现在统一最后两种类型。它通过将约束与合并类型分开合并来实现这一点,因此它将xString 合并以在类型级别上找出x = String
      4. GHC 将约束传播到结果中,表示类型为Num String =&gt; String
      5. GHC 抱怨是因为它不知道NumString 字典是什么样的。

      你如何解决这个问题?最简单的方法是使用 Haskell 自带的数据结构data Either x y = Left x | Right y

      let f x = if x then Left 55 else Right "foo"
      

      那么输出的类型就是Num x =&gt; Either x String,完全没问题,根本没有把两者统一起来。 注意:由于一个叫做“单态限制”的东西,它在 GHC 而不是 GHCi 中默认设置,如果你不提供明确的类型注释,f 可能会减少Num a =&gt; Either a StringEither Integer String

      Prelude> :set -XMonomorphismRestriction
      Prelude> let f x = if x then Left 55 else Right "foo"
      Prelude> :t f
      f :: Num a => Bool -> Either a [Char]
      Prelude> let f = \x -> if x then Left 55 else Right "foo"
      Prelude> :t f
      f :: Bool -> Either Integer [Char]
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-11-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-11-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多