typevariable a和b

时间:2017-07-03 08:43:41

标签: haskell

我正在学习haskell,其中一个棘手的部分是类型变量 请考虑以下示例:

Prelude> :t fmap
fmap :: Functor f => (a -> b) -> f a -> f b

有类型变量ab,它们可以是任何类型。 f是一种必须实现Functor的类型。

让我们为fmap的第一个参数定义一个函数:

Prelude> let replaceWithP = const 'p'

现在,我将函数replaceWithP传递给fmap并查看类型签名:

Prelude> :t fmap replaceWithP
fmap replaceWithP :: Functor f => f b -> f Char

为什么f a变为f b,为什么不归a

2 个答案:

答案 0 :(得分:3)

首先是类型

fmap replaceWithP :: Functor f => f b -> f Char

完全等同于

fmap replaceWithP :: Functor f => f a -> f Char

因为所有类型变量都是隐式普遍量化的,因此可以随意重命名(这称为alpha转换)。

人们可能仍然想知道GHC打印的名称b来自哪里。毕竟,fmap在其类型中有f a,为什么GHC选择将其重命名为b

这里的“罪魁祸首”是replaceWithP

> :t const
const :: a -> b -> a
> let replaceWithP = const 'p'
> :t replaceWithP 
replaceWithP :: b -> Char

因此,b来自replaceWithP

的类型

答案 1 :(得分:3)

类型变量可以被认为是正常变量,除了你有类型。

这是什么意思?例如,C中的变量a可能定义为:

int a = 2;

您可以分配a的可能值是多少?整个int范围,因为它是a可能采用的的集合。让我们在伪Haskell中看一下这个:

type b = Int

b可能采用的值集是什么?这是一个棘手的问题。通常,我们会习惯将2"hello"True等内容视为值。但是,在Haskell中,我们还允许将类型视为值。有点。我们假设b可以采用任何kind形式*。从本质上讲,这包括所有不需要额外信息的类型:

data Tree a = Leaf a | Branch (Tree a) (Tree a)
Tree      -- no, has kind: * -> *
Tree Int  -- okay!
Int       -- okay!
String    -- okay!

这意味着在您的示例中:

fmap :: Functor f => (a -> b) -> f a -> f b

变量ab可以被认为是可以采用任何形式类型的变量,前提是您决定提供它的类型在适当的类型值范围内(受限于种)。

现在更准确地回答你的问题:为什么我们会注意到:

fmap              :: Functor f => (a -> b) -> f a -> f b
fmap replaceWithP :: Functor f =>             f b -> f Char

让我重写以下等效定义,因为变量命名会引起混淆:

fmap              :: Functor f => (a -> b) -> f a -> f b
fmap replaceWithP :: Functor f =>             f z -> f Char

希望现在看起来更加清晰。当您提供replaceWithP :: x -> Char函数时,会发生以下映射:

-- Function types
fmap         :: Functor f => (a -> b) -> f a -> f b
replaceWithP ::              x -> Char

-- Type variable mappings
a -> x
b -> Char

如果我们进行替换,这会是什么样的?

Functor f => (x -> Char) -> f x -> f Char

replaceWithP函数中提供后,您将使用第一个参数,因此您将离开:

fmap replaceWithP :: Functor f => f x -> f Char

或等效地:

fmap replaceWithP :: Functor f => f b -> f Char