標籤:

haskell 怎麼按數據類型來選擇不同構造器?

初學,在看《Write Yourself a Scheme in 48 Hours》遇到了這樣一個問題。

data LispVal = Number Integer | Real Double

將值取出來用,我是這麼做的

unpack::Num a=&>LispVal-&>a
unpack (Number a) = a
unpack (Real a) = a

感覺有點怪怪的,這麼做將Integer 和Double 都提升成了Num a了。

而且更重要的問題是無法將值放回去。。。

pack::Num a=&>a-&>LispVal
--不知道怎麼寫 T_T

怎麼辦?


我覺得《SICP》的 2.5.2 這一節 (中文書 132 頁,英文版鏈接: http://sarabander.github.io/sicp/html/2_002e5.xhtml#g_t2_002e5_002e2) 對你這種情況給出了一種比較好的解決方案。雖然《SICP》的教學語言是 Scheme,既然你在寫 Scheme 解釋器,那麼應該不難看懂課本上的代碼。

用 Haskell 描述此方法大致是這樣(有的細節可能有錯,沒有驗證):

加上題主在評論里提到的分數,題中數據類型定義為:

data LispVal = Number Integer | Fraction (Ratio Int) | Real Double

這裡我們假定數字之間的轉換隻能按以下方向進行:

整數 → 分數 → 浮點數

再寫個用於「類型提升」的函數:

promote :: LispVal -&> LispVal
promote (Number n) = Fraction $ n % 1
promote (Fraction n) = Real $ ...
promote _ = Error "Cannot ..."

對於加減乘除等每一類運算,都只寫出同類型的方法。例如加法:

type LispValBinOp = LispVal -&> LispVal -&> LispVal

add :: LispValBinOp
add (Number a) (Number b) = Number $ a + b
add (Real a) (Real b) = Real $ a + b
add (Fraction a) (Fraction b) = Fraction $ a + b

定義一個處理各種二元運算的高階函數:

applyBinOp :: LispValBinOp -&> LispValBinOp
applyBinOp f (Number a) (Number b) = f a b
applyBinOp f (Fraction a) (Fraction b) = f a b
applyBinOp f (Real a) (Real b) = f a b
-- To Real
applyBinOp f (Real a) b = applyBinOp (Real a) (promote b)
applyBinOp f a (Real b) = applyBinOp (promote a) (Real b)
-- To Fraction
applyBinOp f (Fraction a) b = apply (Fraction a) (promote b)
applyBinOp f a (Fraction b) = apply (promote a) (Fraction b)

最後,兩個數相加的時候,不要直接調用 add,而是用一個通過一個高階函數間接調用 add:

applyBinOp add lhs rhs

以後要加新的二元運算的時候,就只需要處理三種類型匹配的情況,而不必分多種情況寫一大堆重複的代碼了。


Num a =&> a是可以特化到Integer/Double的,但是反之不亦然啊,unpack的實現是通不過編譯的。你得全程保留LispVal類型,或者為了方便起見暫時只用Double。


這是典型的gadt問題,你可以這樣解

{-# LANGUAGE GADTs #-}

data LispVal a where

Number :: integer -&> lispval integer

real :: double -&> lispval double

unpack :: lispval a -&> a

unpack (number a) = a

unpack (real a) = a

class lispvalc a where

pack :: a -&> lispval a

instance lispvalc integer where

pack a = number a

instance lispvalc double where

pack a = real a

我手機打的,大小寫和縮進你需要調整一下


推薦閱讀:

haskell中的immutable array是如何實現隨機訪問的?
精通 Haskell 是一種怎樣的體驗?
使用 Haskell 編寫靈活的 Parser (上)
編程語言中的「組合性」是什麼意思?

TAG:Haskell |