【问题标题】:What is the difference between typevariable a and b类型变量a和b有什么区别
【发布时间】:2017-07-03 08:43:40
【问题描述】:

我正在学习 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

【问题讨论】:

  • GHCi打印出来的名字在这里没有什么特殊含义。
  • 好的,所以b 可以是任意类型。
  • ab 可能属于不同的类型,但并非必须如此。 f 是一个多态类型,必须有函子约束。
  • 那么当一个函数具有以下签名a -> b,那么a类型不必与b类型不同?可以是同一类型吗?
  • 首先,类型变量的标识不对应于用于命名它们的文字字符,除非在特定类型的本地范围内。所以forall a b . a -> bforall a b . b -> a精确地 相同的类型。其次,事实上,a -> b 并不意味着ab 必须不同——一个类型变量可以采用任何类型,包括另一个类型变量。

标签: haskell


【解决方案1】:

一、类型

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 的类型。

【讨论】:

    【解决方案2】:

    类型变量可以被认为是普通变量,除非你有类型。

    这是什么意思?例如,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
    

    【讨论】:

      猜你喜欢
      • 2012-07-03
      • 1970-01-01
      • 2018-06-27
      • 2013-06-25
      • 2021-02-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-09
      相关资源
      最近更新 更多