对Haskell多态类型感到困惑

时间:2015-05-13 14:08:04

标签: haskell

我已经定义了一个函数:

gen :: a -> b

所以只是尝试提供一个简单的实现:

gen 2 = "test"

但抛出错误:

gen.hs:51:9:
    Couldn't match expected type ‘b’ with actual type ‘[Char]’
      ‘b’ is a rigid type variable bound by
          the type signature for gen :: a -> b at gen.hs:50:8
    Relevant bindings include gen :: a -> b (bound at gen.hs:51:1)
    In the expression: "test"
    In an equation for ‘gen’: gen 2 = "test"
Failed, modules loaded: none.

所以我的功能不正确。为什么a没有输入为Int而b没有输入为String?

5 个答案:

答案 0 :(得分:11)

这是一个非常普遍的误解。

要理解的关键是,如果您的类型签名中有变量,那么调用者可以决定什么类型,不是您!

所以你不能说"这个函数返回类型x"然后返回String;你的函数实际上必须能够返回调用者可能要求的任何可能的类型。如果我要求您的函数返回Int,则必须返回Int。如果我要求它返回Bool,则必须返回Bool

您的函数声明能够返回任何可能的类型,但实际它只会返回String。所以它并没有做类型签名声称它做的事情。因此,编译时错误。

很多人显然误解了这一点。在(比方说)Java中,您可以说"此函数返回Object",然后您的函数可以返回它想要的任何内容。所以函数决定它返回的类型。在Haskell中,调用者决定返回什么类型,而不是函数。

修改:请注意,您所写的类型a -> b是不可能的。没有任何功能永远具有此类型。功能无法凭空建立b类型的值。唯一可行的方法是,如果某些输入还涉及类型b,或者b属于某种允许值构造的类型类。

例如:

head :: [x] -> x

此处的返回类型为x("任何可能的类型"),但输入类型也提到x,因此此功能是可能的;你只需要返回原始列表中的一个值。

同样,gen :: a -> a是完全有效的功能。但它唯一能做的就是不改变它的输入(即id函数的作用)。

类型签名的这个属性告诉你函数是做什么的,这是Haskell非常有用和强大的属性。

答案 1 :(得分:7)

gen :: a -> b并不意味着“某些类型a而某些类型bfoo必须属于a -> b类型”,这意味着“for < em>任何类型a任何类型bfoo必须属于a -> b类型。

激励这一点:如果类型检查器看到类似let x :: Int = gen "hello"的内容,则会在此处看到gen用作String -> Int,然后查看gen的类型看看它是否可以这样使用。类型为a -> b,可以专门用于String -> Int,因此类型检查器会确定这很好,并允许此调用。这是因为声明函数具有类型a -> b,类型检查器允许您使用任何类型调用函数,并允许您将结果用作任何类型。

然而,这显然与您给予该功能的定义不符。该函数知道如何将数字作为参数处理 - 没有别的。同样,它知道如何生成字符串作为结果 - 没有别的。很明显,不应该用字符串作为参数来调用函数,或者将函数的结果用作Int。因此类型a -> b允许这样做,显然该函数的类型错误。

答案 2 :(得分:2)

您的类型签名gen :: a -> b声明,您的函数可以用于任何类型a(并提供任何类型b 调用者功能要求)。

除了很难得到这样的功能之外,行gen 2 = "test"试图返回String,这很可能不是调用者所要求的。

答案 3 :(得分:1)

优秀的答案。但是,鉴于您的个人资料,您似乎了解Java,因此我认为将其连接到Java也很有价值。

Java提供两种多态性:

  1. 子类型多态:,例如,每种类型都是java.lang.Object
  2. 的子类型
  3. 通用多态:,例如List<T>界面。
  4. Haskell的类型变量是(2)的一个版本。 Haskell并没有真正拥有(1)的版本。

    考虑泛型多态性的一种方法是使用 templates (这是C ++人们称之为):具有类型变量参数的类型是可以专用的模板分为各种单态类型。因此,例如,接口List<T>是用于构造单态接口的模板,如List<String>List<List<String>>等等,所有这些接口都具有相同的结构,但仅因为类型变量{{而不同1}}在实例化类型的整个签名中均匀替换。

    &#34;来电者选择的概念&#34;几个响应者在这里提到的基本上是一种引用实例化的友好方式。例如,在Java中,类型变量被选择的最常见点是&#34;是实例化对象时:

    T

    第二个共同点是泛型类型的子类型可以实例化超类型的变量:

    List<String> myList = new ArrayList<String>();
    

    第三种方法允许调用者实例化一个不是其封闭类型参数的变量:

    class MyFunction implements Function<Integer, String> {
        public String apply(Integer i) { ... }
    }
    

    在Haskell中,实例化由类型推断算法隐式执行。在使用/** * Visitor-pattern style interface for a simple arithmetical language * abstract syntax tree. */ interface Expression { // The caller of `accept` implicitly chooses which type `R` is, // by supplying a `Visitor<R>` with `R` instantiated to something // of its choice. <R> accept(Expression.Visitor<R> visitor); static interface Visitor<R> { R constant(int i); R add(Expression a, Expression b); R multiply(Expression a, Expression b); } } 的任何表达式中,类型推断将根据使用gen :: a -> b的上下文推断需要为ab实例化的类型。所以基本上,&#34;来电者选择&#34;表示使用gen的任何代码都会控制gena将被实例化的类型;如果我写b,那么我会隐式地将gen [()]实例化为a。这里的错误意味着您的类型声明表明允许使用[()],但您的等式gen [()]暗示它不是。

答案 4 :(得分:0)

在Haskell中,类型变量是隐式量化的,但我们可以明确说明:

{-# LANGUAGE ScopedTypeVariables #-}

gen :: forall a b . a -> b
gen x = ????

“forall”实际上只是lambda的类型级版本,通常写成Λ。所以gen是一个带有三个参数的函数:一个类型,绑定到名称a,另一个类型绑定到名称b,值类型a,绑定到名称x。调用函数时,将使用这三个参数调用它。考虑一个更健全的案例:

fst :: (a,b) -> a
fst (x1,x2) = x1

这会转换为

fst :: forall (a::*) (b::*) . (a,b) -> a
fst = /\ (a::*) -> /\ (b::*) -> \ (x::(a,b)) ->
   case x of
      (x1, x2) -> x1

其中*是普通具体类型的类型(通常称为 kind )。如果我致电fst (3::Int, 'x'),则会转换为

fst Int Char (3Int, 'x')

我使用3Int来明确表示Int 3版本的fst Int Char (3Int, 'x') = (/\ (a::*) -> /\ (b::*) -> \(x::(a,b)) -> case x of (x1,x2) -> x1) Int Char (3Int, 'x') = (/\ (b::*) -> \(x::(Int,b)) -> case x of (x1,x2) -> x1) Char (3Int, 'x') = (\(x::(Int,Char)) -> case x of (x1,x2) -> x1) (3Int, x) = case (3Int,x) of (x1,x2) -> x1 = 3Int 。然后我们可以按如下方式计算:

fst

无论我传入哪种类型,只要我传入的值匹配,a->b函数就能生成所需类型的东西。如果您尝试为{{1}}执行此操作,则会遇到问题。