[PLT] 柯里化的前生今世(十二):多態(tài)性

關(guān)于

本文借用Haskell介紹了自定義類型,帶參數(shù)的類型,Ad-hoc多態(tài)性,kind,
其中,帶參數(shù)的類型在類型上可以做“柯里化”。

1. 自定義類型

Haskell中使用data自定義類型。

data Bool = True | False

其中,Bool是類型名,TrueFalse是該類型的值。

一個(gè)值可以包含多種不同類型的字段(field),例如,

data BookType = BookValue Int String

其中BookType是類型名,BookValue是值構(gòu)造器(Value constructor)。
一個(gè)BookType類型的值,可以這樣定義,

bookValue = BookValue 12 "ab"

注:
類型名和值構(gòu)造器會(huì)在不同上下文中使用,
因此,常把它們寫成同一個(gè)名字,應(yīng)當(dāng)注意區(qū)分。

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雖然類型不同,但是計(jì)算過程相同,
如果我們想計(jì)算不同類型列表的長度,就需要寫多個(gè)不同的函數(shù),
這違背了軟件工程中基本的設(shè)計(jì)原則:

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指的是類型,
我們需要提取出一個(gè)抽象的類型[a],
然后再實(shí)例化為不同的具體類型[Int][Char]

3. 多態(tài)類型系統(tǒng)

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).

如果一個(gè)類型系統(tǒng),允許一段代碼在不同的上下文中具有不同的類型,
這樣的類型系統(tǒng)就稱為多態(tài)類型系統(tǒng)。

現(xiàn)代編程語言,包含了不同形式的多態(tài)性:
參數(shù)化多態(tài),Ad-hoc多態(tài),子類型多態(tài)。

(1)參數(shù)化多態(tài)(Parametric polymorphism)

data Maybe a = Just a | Nothing

以上代碼定義了一個(gè)帶參數(shù)的類型Maybe,它不能表示某個(gè)值的類型,
Maybe Int放在一起,才是一個(gè)具體的類型,
類型名Maybe也稱為類型構(gòu)造器(Type constructor)。

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

如果某個(gè)函數(shù)(函數(shù)是一種值)的類型是一個(gè)帶參數(shù)的類型,
函數(shù)的計(jì)算過程就可以寫到一個(gè)地方,
不同的調(diào)用場(chǎng)景,會(huì)將length函數(shù)具體化為不同的類型。

length [1,2,3]length被具體化為[Int] -> Int,
length ['a','b','c']length被具體化為[Char] -> Char。

(2)Ad-hoc多態(tài)(Ad-hoc polymorphism)

某個(gè)Ad-hoc多態(tài)類型的值,看做不同類型時(shí),會(huì)有不同的行為。
重載(Overloading)就是一種Ad-hoc多態(tài),
它可以讓一個(gè)函數(shù)具有不同的實(shí)現(xiàn)。
每次函數(shù)調(diào)用,編譯器(或運(yùn)行時(shí)系統(tǒng))會(huì)選擇適當(dāng)?shù)膶?shí)現(xiàn)。

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多態(tài),
函數(shù)isEqual在具體化為不同的類型時(shí),可以有不同的實(shí)現(xiàn)。

(3)子類型多態(tài)(Subtype polymorphism)

它允許某個(gè)類型的值,在包含關(guān)系上,可以看做它也是父類型的值。
在面向?qū)ο笳Z言社區(qū),經(jīng)常把子類型多態(tài),簡(jiǎn)稱為多態(tài)。

注:
同一種語言可能具有不同的多態(tài)性,
例如,Standard ML提供了參數(shù)化多態(tài),內(nèi)置運(yùn)算符重載(Ad-hoc多態(tài)),
但是沒有提供子類型多態(tài)。
而Java提供了子類型多態(tài),函數(shù)重載(Ad-hoc多態(tài)),泛型(參數(shù)化多態(tài))。

4. kind

關(guān)于帶參數(shù)的類型,
與函數(shù)Currying相似,類型構(gòu)造器也可以『Currying』,
例如,我們定義以下帶兩個(gè)參數(shù)的Either類型,

data Either a b = Left a | Right b

其中Either是一個(gè)類型構(gòu)造器,接受兩個(gè)類型參數(shù)IntString,
得到具體類型Either Int String

如果只提供一個(gè)類型參數(shù)呢?
Either Int仍然是一個(gè)類型構(gòu)造器,它接受一個(gè)類型參數(shù)String,
得到具體類型Either Int String。

正因?yàn)橛羞@種差異性,
Haskell中使用kind來區(qū)分不同的類型。

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. >>=的多態(tài)性

函數(shù)>>=定義在Monad typeclass中,

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

其中,mMonad typeclass的實(shí)例類型,它的kind* -> *
類型m a是一個(gè)具體類型,該類型的值稱為monad value。

我們看到,在m確定的情況下,>>=的類型簽名中仍然包含類型變量。
因此,對(duì)于Monad typeclass的某個(gè)實(shí)例來說,
>>=可以操作相同m類型但是不同a類型的monad value :: m a。

Monad typeclass的實(shí)例IO為例,對(duì)于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個(gè)IO action的類型吧,

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

它們的具體類型都是m a,
m相同,m = IO
a不同,分別是( )String,( )。

我們知道do notation是>>=的語法糖,我們將do notation轉(zhuǎn)換成>>=的串聯(lián)形式,

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

對(duì)于第一個(gè)>>=,我們能推斷出它的大概類型,

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

其中“?”表示尚未確定的類型。

而第二個(gè)>>=的類型,可以完全確定下來。

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

最后,第一個(gè)>>=的類型也就可以完全確定下來了,

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

由此可見,
第一個(gè)>>= :: IO ( ) -> (( ) -> IO ( )) -> IO ( )
第二個(gè)>>= :: IO String -> (String -> IO ( )) -> IO ( )
兩個(gè)>>=的類型不同,它們同時(shí)出現(xiàn)了。

因此,在參數(shù)化類型中,類型變量的解和數(shù)學(xué)方程中未知數(shù)的解,意義不同,
在數(shù)學(xué)方程中,同名未知數(shù)對(duì)應(yīng)相同的解,
而在不同的程序上下文中,同名類型變量可以被確定為不同的具體類型。

當(dāng)類型具有多態(tài)性時(shí),為了讓整個(gè)程序類型合法,
類型變量就必須滿足各個(gè)表達(dá)式的類型約束條件(constraints),
只不過,不同位置的類型變量取值可以不同。

>>=的定義中包含了參量m,ab

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

在同一個(gè)程序中,m確定為IOa在有的地方確定為( ),有的地方確定為String

確定這些類型變量的過程,稱為類型重建(type reconstruction),
有時(shí)也稱為類型推導(dǎo)(type inference)。

6. 1 :: Num a => a

我們來看看1的類型

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

這說明1具有Ad-hoc多態(tài)性,它是Num typeclass中定義的值。

在使用Haskell的typeclass時(shí),如果和面向?qū)ο笳Z言中的interface類比,
就很容易產(chǎn)生一個(gè)誤區(qū),認(rèn)為typeclass中只能定義函數(shù)。
而實(shí)際上,typeclass中定義了一些具有Ad-hoc多態(tài)類型的值。
這個(gè)值,當(dāng)然可以是函數(shù)類型的。

例如:

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

我們來檢測(cè)下:

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

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

注:
在Haskell規(guī)范中并不是這樣解釋字面量1的,

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

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

整數(shù)字面量之所以用這種間接的方式來定義,
是為了讓它們具有Ad-hoc多態(tài)性,
可以在Num typeclass不同的實(shí)例類型中使用它們。

7. 參考:

Polymorphism
Types and Programming Languages
Haskell 2010 Language Report

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容