[PLT] 柯里化的前生今世(十二):多态性

关于

本文借用Haskell介绍了自定义类型,带参数的类型,Ad-hoc多态性,kind,
其中,带参数的类型在类型上可以做“柯里化”。

1. 自定义类型

Haskell中使用data自定义类型。

data Bool = True | False

其中,Bool是类型名,TrueFalse是该类型的值。

一个值可以包含多种不同类型的字段(field),例如,

data BookType = BookValue Int String

其中BookType是类型名,BookValue是值构造器(Value constructor)。
一个BookType类型的值,可以这样定义,

bookValue = BookValue 12 "ab"

注:
类型名和值构造器会在不同上下文中使用,
因此,常把它们写成同一个名字,应当注意区分。

data Book = Book Int String

2. 抽象与具体化

lengthInt :: [Int] -> Int
lengthInt [] = 0
lengthInt (_:xs) = 1 + lengthInt xs

lengthChar :: [Char] -> Int
lengthChar [] = 0
lengthChar (_:xs) = 1 + lengthChar xs

lengthIntlengthChar虽然类型不同,但是计算过程相同,
如果我们想计算不同类型列表的长度,就需要写多个不同的函数,
这违背了软件工程中基本的设计原则:

Abstraction principle:
Each significant piece of functionality in a program should be implemented in just one place in the source code. Where similar functions are carried out by distinct pieces of code, it is generally beneficial to combine them into one by abstracting out the varying parts.

和通常的代码不同,这里提到的varying parts指的是类型,
我们需要提取出一个抽象的类型[a]
然后再实例化为不同的具体类型[Int][Char]

3. 多态类型系统

Type systems that allow a single piece of code to be used with multiple types are collectively known as polymorphic systems (poly = many, morph = form).

如果一个类型系统,允许一段代码在不同的上下文中具有不同的类型,
这样的类型系统就称为多态类型系统。

现代编程语言,包含了不同形式的多态性:
参数化多态,Ad-hoc多态,子类型多态。

(1)参数化多态(Parametric polymorphism)

data Maybe a = Just a | Nothing

以上代码定义了一个带参数的类型Maybe,它不能表示某个值的类型,
Maybe Int放在一起,才是一个具体的类型,
类型名Maybe也称为类型构造器(Type constructor)。

length :: [a] -> Int
length [] = 0
length (_:xs) = 1 + length xs

如果某个函数(函数是一种值)的类型是一个带参数的类型,
函数的计算过程就可以写到一个地方,
不同的调用场景,会将length函数具体化为不同的类型。

length [1,2,3]length被具体化为[Int] -> Int
length ['a','b','c']length被具体化为[Char] -> Char

(2)Ad-hoc多态(Ad-hoc polymorphism)

某个Ad-hoc多态类型的值,看做不同类型时,会有不同的行为。
重载(Overloading)就是一种Ad-hoc多态,
它可以让一个函数具有不同的实现。
每次函数调用,编译器(或运行时系统)会选择适当的实现。

class BasicEq a where 
    isEqual :: a -> a -> Bool

instance BasicEq Bool where 
    isEqual True True = True 
    isEqual False False = True 
    isEqual _ _ = False

Haskell中的typeclass使用了Ad-hoc多态,
函数isEqual在具体化为不同的类型时,可以有不同的实现。

(3)子类型多态(Subtype polymorphism)

它允许某个类型的值,在包含关系上,可以看做它也是父类型的值。
在面向对象语言社区,经常把子类型多态,简称为多态。

注:
同一种语言可能具有不同的多态性,
例如,Standard ML提供了参数化多态,内置运算符重载(Ad-hoc多态),
但是没有提供子类型多态。
而Java提供了子类型多态,函数重载(Ad-hoc多态),泛型(参数化多态)。

4. kind

关于带参数的类型,
与函数Currying相似,类型构造器也可以『Currying』,
例如,我们定义以下带两个参数的Either类型,

data Either a b = Left a | Right b

其中Either是一个类型构造器,接受两个类型参数IntString
得到具体类型Either Int String

如果只提供一个类型参数呢?
Either Int仍然是一个类型构造器,它接受一个类型参数String
得到具体类型Either Int String

正因为有这种差异性,
Haskell中使用kind来区分不同的类型。

ghci> :k Int
Int :: *

ghci> :k Maybe
Maybe :: * -> *

ghci> :k Maybe Int
Maybe Int :: *

ghci> :k Either
Either :: * -> * -> *

ghci> :k Either Int
Either Int :: * -> *

ghci> :k Either Int String
Either Int String :: *

5. >>=的多态性

函数>>=定义在Monad typeclass中,

class Monad m where
    (>>=) :: m a -> (a -> m b) -> m b

其中,mMonad typeclass的实例类型,它的kind* -> *
类型m a是一个具体类型,该类型的值称为monad value。

我们看到,在m确定的情况下,>>=的类型签名中仍然包含类型变量。
因此,对于Monad typeclass的某个实例来说,
>>=可以操作相同m类型但是不同a类型的monad value :: m a

Monad typeclass的实例IO为例,对于IO来说,IO monad value称为IO action。

main :: IO ( )
main = do
    putStrLn "Please input: "
    inpStr <- getLine
    putStrLn $ "Hello " ++ inpStr

其中,putStrLn :: String -> IO ( )getLine :: IO String

我们来分析一下这3个IO action的类型吧,

putStrLn "Please input: " :: IO ( )
getLine :: IO String
putStrLn $ "Hello " ++ inpStr :: IO ( )

它们的具体类型都是m a
m相同,m = IO
a不同,分别是( )String( )

我们知道do notation是>>=的语法糖,我们将do notation转换成>>=的串联形式,

(putStrLn "Please input: ") >>= (\ _ -> (getLine >>= (\inpStr -> (putStrLn $ "Hello " ++ inpStr ))))

对于第一个>>=,我们能推断出它的大概类型,

>>= :: IO ( ) -> (( ) -> IO ?) -> IO ?

其中“?”表示尚未确定的类型。

而第二个>>=的类型,可以完全确定下来。

>>= :: IO String -> (String -> IO ( )) -> IO ( )

最后,第一个>>=的类型也就可以完全确定下来了,

>>= :: IO ( ) -> (( ) -> IO ( )) -> IO ( )

由此可见,
第一个>>= :: IO ( ) -> (( ) -> IO ( )) -> IO ( )
第二个>>= :: IO String -> (String -> IO ( )) -> IO ( )
两个>>=的类型不同,它们同时出现了。

因此,在参数化类型中,类型变量的解和数学方程中未知数的解,意义不同,
在数学方程中,同名未知数对应相同的解,
而在不同的程序上下文中,同名类型变量可以被确定为不同的具体类型。

当类型具有多态性时,为了让整个程序类型合法,
类型变量就必须满足各个表达式的类型约束条件(constraints),
只不过,不同位置的类型变量取值可以不同。

>>=的定义中包含了参量mab

(>>=) :: m a -> (a -> m b) -> m b

在同一个程序中,m确定为IOa在有的地方确定为( ),有的地方确定为String

确定这些类型变量的过程,称为类型重建(type reconstruction),
有时也称为类型推导(type inference)。

6. 1 :: Num a => a

我们来看看1的类型

ghci> :t 1
1 :: Num a => a

这说明1具有Ad-hoc多态性,它是Num typeclass中定义的值。

在使用Haskell的typeclass时,如果和面向对象语言中的interface类比,
就很容易产生一个误区,认为typeclass中只能定义函数。
而实际上,typeclass中定义了一些具有Ad-hoc多态类型的值。
这个值,当然可以是函数类型的。

例如:

class TypeClassWithValue t where 
    plainValue :: t 
    functionValue :: t -> t

我们来检测下:

ghci> :t plainValue
plainValue :: TypeClassWithValue t => t

ghci> :t functionValue
functionValue :: TypeClassWithValue t => t -> t

注:
在Haskell规范中并不是这样解释字面量1的,

An integer literal represents the application of the function fromInteger to the appropriate value of type Integer.

其中fromInteger :: (Num a) => Integer -> a
因此,整数字面量的类型就是(Num a) => a了。

整数字面量之所以用这种间接的方式来定义,
是为了让它们具有Ad-hoc多态性,
可以在Num typeclass不同的实例类型中使用它们。

7. 参考:

Polymorphism
Types and Programming Languages
Haskell 2010 Language Report

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容