简体   繁体   English

对Haskell多态类型感到困惑

[英]Confused about Haskell polymorphic types

I have defined a function : 我定义了一个函数:

gen :: a -> b

So just trying to provide a simple implementation : 所以只是尝试提供一个简单的实现:

gen 2 = "test"

But throws error : 但抛出错误:

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.

So my function is not correct. 所以我的功能不正确。 Why is a not typed as Int and b not typed as String ? 为什么a没有int类型和b类型不能为字符串?

This is a very common misunderstanding. 这是一个非常普遍的误解。

The key thing to understand is that if you have a variable in your type signature, then the caller gets to decide what type that is, not you! 要理解的关键是,如果你的类型签名中有一个变量,那么调用者就可以决定它是什么类型,而不是你!

So you cannot say "this function returns type x " and then just return a String ; 所以你不能说“这个函数返回类型x ”然后只返回一个String ; your function actually has to be able to return any possible type that the caller may ask for. 你的函数实际上必须能够返回调用者可能要求的任何可能的类型 If I ask your function to return an Int , it has to return an Int . 如果我要求你的函数返回一个Int ,它必须返回一个Int If I ask it to return a Bool , it has to return a Bool . 如果我要求它返回Bool ,它必须返回Bool

Your function claims to be able to return any possible type, but actually it only ever returns String . 您的函数声称能够返回任何可能的类型,但实际上它只返回String So it doesn't do what the type signature claims it does. 所以它没有做类型签名所声称的那样做。 Hence, a compile-time error. 因此,编译时错误。

A lot of people apparently misunderstand this. 很多人显然误解了这一点。 In (say) Java, you can say "this function returns Object ", and then your function can return anything it wants. 在(比方说)Java中,您可以说“此函数返回Object ”,然后您的函数可以返回它想要的任何内容。 So the function decides what type it returns. 所以函数决定它返回什么类型。 In Haskell, the caller gets to decide what type is returned, not the function. 在Haskell中, 调用者可以决定返回什么类型,而不是函数。

Edit: Note that the type you're written, a -> b , is impossible. 编辑:请注意,您写的类型a -> b是不可能的。 No function can ever have this type. 无功能不能有这种类型。 There's no way a function can construct a value of type b out of thin air. 功能无法凭空建立b型值。 The only way this can work is if some of the inputs also involve type b , or if b belongs to some kind of typeclass which allows value construction. 唯一可行的方法是,如果某些输入也涉及类型b ,或者b属于某种允许值构造的类型类。

For example: 例如:

head :: [x] -> x

The return type here is x ("any possible type"), but the input type also mentions x , so this function is possible; 这里的返回类型是x (“任何可能的类型”),但输入类型也提到x ,所以这个函数是可能的; you just have to return one of the values that was in the original list. 您只需返回原始列表中的一个值。

Similarly, gen :: a -> a is a perfectly valid function. 同样, gen :: a -> a是一个完全有效的函数。 But the only thing it can do is return it's input unchanged (ie, what the id function does). 但它唯一能做的就是返回它的输入不变(即id函数的作用)。

This property of type signatures telling you what a function does is a very useful and powerful property of Haskell. 类型签名的这个属性告诉你函数的功能是Haskell非常有用和强大的属性。

gen :: a -> b does not mean "for some type a and some type b , foo must be of type a -> b ", it means "for any type a and any type b , foo must be of type a -> b ". gen :: a -> b并不意味着“对于某些类型a和某些类型bfoo必须是a -> b ”类型,它意味着“对于任何类型a任何类型bfoo必须是a -> b类型a -> b “。

to motivate this: If the type checker sees something like let x :: Int = gen "hello" , it sees that gen is used as String -> Int here and then looks at gen 's type to see whether it can be used that way. 激励这个:如果类型检查器看到类似let x :: Int = gen "hello" ,它会看到gen在这里用作String -> Int然后查看gen的类型以查看是否可以使用它方式。 The type is a -> b , which can be specialized to String -> Int , so the type checker decides that this is fine and allows this call. 类型是a -> b ,可以专门用于String -> Int ,因此类型检查器决定这很好并允许此调用。 That is since the function is declared to have type a -> b , the type checker allows you to call the function with any type you want and allows you to use the result as any type you want. 这是因为声明函数具有类型a -> b ,类型检查器允许您使用任何类型调用函数,并允许您将结果用作所需的任何类型。

However that clearly does not match the definition you gave the function. 但是,这显然与您给出的功能定义不符。 The function knows how to handle numbers as arguments - nothing else. 该函数知道如何将数字作为参数处理 - 没有别的。 And likewise it knows how to produce strings as its result - nothing else. 同样,它知道如何生成字符串作为结果 - 没有别的。 So clearly it should not be possible to call the function with a string as its argument or to use the function's result as an Int. 很明显,不应该用字符串作为参数来调用函数,或者将函数的结果用作Int。 So since the type a -> b would allow that, it's clearly the wrong type for that function. 因此,由于类型a -> b允许,因此该函数显然是错误的类型。

Your type signature gen :: a -> b is stating, that your function can work for any type a (and provide any type b the caller of the function demands). 您的类型签名gen :: a -> b表示,您的函数可以适用于任何类型a(并提供任何类型b函数需求的调用者 )。

Besides the fact that such a function is hard to come by, the line gen 2 = "test" tries to return a String which very well may not be what the caller demands. 除了很难得到这样的功能之外,行gen 2 = "test"试图返回一个String ,这很可能不是调用者所要求的。

Excellent answers. 优秀的答案。 Given your profile, however, you seem to know Java, so I think it's valuable to connect this to Java as well. 但是,鉴于您的个人资料,您似乎了解Java,因此我认为将此连接到Java也很有价值。

Java offers two kinds of polymorphism: Java提供了两种多态性:

  1. Subtype polymorphism: eg, every type is a subtype of java.lang.Object 子类型多态性:例如,每个类型都是java.lang.Object的子类型
  2. Generic polymorphism: eg, in the List<T> interface. 通用多态性:例如,在List<T>接口中。

Haskell's type variables are a version of (2). Haskell的类型变量是(2)的一个版本。 Haskell doesn't really have a version of (1). Haskell实际上没有(1)的版本。

One way to think of generic polymorphism is in terms of templates (which is what C++ people call them): a type that has a type variable parameter is a template that can be specialized into a variety of monomorphic types. 考虑泛型多态的一种方法是模板 (这是C ++人们称之为模板 ):具有类型变量参数的类型是可以专门用于各种单形类型的模板。 So for example, the interface List<T> is a template for constructing monomorphic interfaces like List<String> , List<List<String>> and so on, all of which have the same structure but differ only because the type variable T gets replaced uniformly throughout the signatures with the instantiation type. 因此,例如,接口List<T>是用于构造单态接口的模板,如List<String>List<List<String>>等等,所有这些都具有相同的结构但不同之处仅在于因为类型变量T得到使用实例化类型在整个签名中统一替换。

The concept that "the caller chooses" that several responders have mentioned here is basically a friendly way of referring to instantiation. 几个响应者在这里提到的“呼叫者选择”的概念基本上是一种引用实例化的友好方式。 In Java, for example, the most common point where the type variable gets "chosen" is when an object is instantiated: 例如,在Java中,类型变量被“选择”的最常见点是实例化对象时:

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

Second common point is that a subtype of a generic type may instantiate the supertype's variables: 第二个共同点是泛型类型的子类型可以实例化超类型的变量:

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

Third one is methods that allow the caller to instantiate a variable that's not a parameter of its enclosing type: 第三种方法允许调用者实例化一个不是其封闭类型参数的变量:

/**
 * 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);
    }
}

In Haskell, instantiation is carried out implicitly by the type inference algorithm. 在Haskell中,实例化由类型推断算法隐式执行。 In any expression where you use gen :: a -> b , type inference will infer what types need to be instantiated for a and b , given the context in which gen is used. 在使用gen :: a -> b任何表达式中,类型推断将推断出需要为ab实例化的类型,给定使用gen的上下文。 So basically, "caller chooses" means that any code that uses gen controls the types to which a and b will be instantiated; 所以基本上,“调用者选择”意味着任何使用gen代码都会控制ab将被实例化的类型; if I write gen [()] , then I'm implicitly instantiating a to [()] . 如果我写gen [()] ,那么我隐式地将a实例化为[()] The error here means that your type declaration says that gen [()] is allowed, but your equation gen 2 = "test" implies that it's not. 这里的错误意味着你的类型声明说允许gen [()] ,但你的等式gen 2 = "test"意味着它不是。

In Haskell, type variables are implicitly quantified, but we can make this explicit: 在Haskell中,类型变量是隐式量化的,但我们可以明确地说明:

{-# LANGUAGE ScopedTypeVariables #-}

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

The "forall" is really just a type level version of a lambda, often written Λ. “forall”实际上只是lambda的类型级版本,通常写成Λ。 So gen is a function taking three arguments: a type, bound to the name a , another type, bound to the name b , and a value of type a , bound to the name x . 因此gen是一个带有三个参数的函数:绑定到名称a类型,绑定到名称b另一个类型,以及绑定到名称x的类型a的值。 When your function is called, it is called with those three arguments. 调用函数时,将使用这三个参数调用它。 Consider a saner case: 考虑一个更健全的案例:

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

This gets translated to 这被翻译成了

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

where * is the type (often called a kind ) of normal concrete types. 其中*是普通混凝土类型的类型(通常称为一种 )。 If I call fst (3::Int, 'x') , that gets translated into 如果我调用fst (3::Int, 'x') ,那就会被转换成

fst Int Char (3Int, 'x')

where I use 3Int to represent specifically the Int version of 3 . 我在哪里用3Int来表示3Int版本。 We could then calculate it as follows: 然后我们可以按如下方式计算:

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

Whatever types I pass in, as long as the value I pass in matches, the fst function will be able to produce something of the required type. 无论我传入哪种类型,只要我传入的值匹配, fst函数就能生成所需类型的东西。 If you try to do this for a->b , you will get stuck. 如果您尝试为a->b执行此操作,则会遇到困难。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM