我已经定义了一个函数:
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?
答案 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
而某些类型b
,foo
必须属于a -> b
类型”,这意味着“for < em>任何类型a
和任何类型b
,foo
必须属于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提供两种多态性:
java.lang.Object
List<T>
界面。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
的上下文推断需要为a
和b
实例化的类型。所以基本上,&#34;来电者选择&#34;表示使用gen
的任何代码都会控制gen
和a
将被实例化的类型;如果我写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}}执行此操作,则会遇到问题。