我正在制作一種玩具語言,我希望能夠使用相同的建構式對浮點數和整數進行算術運算,就像這樣(下面提供的最小示例)。
data Expr a where
NumberConst :: Number a -> Expr (Number a)
Mul :: Expr (Number n) -> Expr (Number n) -> Expr (Number n)
data family Number :: * -> *
data instance Number Integer = NumInt Integer deriving (Eq, Ord, Show)
data instance Number Float = NumFloat Float deriving (Eq, Ord, Show)
class (Eq a, Ord a, Num a) => SquanchyNum a where
toDigit :: Number a -> a
divide :: Number a -> Number a -> a
mul :: Number a -> Number a -> a
sub :: Number a -> Number a -> a
add :: Number a -> Number a -> a
instance SquanchyNum Integer where
toDigit (NumInt x) = x
divide (NumInt x) (NumInt y) = x `div` y
mul (NumInt x) (NumInt y) = x * y
sub (NumInt x) (NumInt y) = x - y
add (NumInt x) (NumInt y) = x y
instance SquanchyNum Float where
toDigit (NumFloat x) = x
divide (NumFloat x) (NumFloat y) = x / y
mul (NumFloat x) (NumFloat y) = x * y
sub (NumFloat x) (NumFloat y) = x - y
add (NumFloat x) (NumFloat y) = x y
eval :: Expr a -> a
eval (NumberConst a) = a
eval (Mul a b) = (eval a) `mul` (eval b) <-- this line makes the problem visible
在 ghci 我得到以下錯誤
*Main> :r
[2 of 3] Compiling Lib ( /home/michael/git/brokensquanchy/src/Lib.hs,
interpreted )
/home/michael/git/brokensquanchy/src/Lib.hs:45:18: error:
? Occurs check: cannot construct the infinite type: n ~ Number n
Expected type: a
Actual type: n
? In the expression: (eval a) `mul` (eval b)
In an equation for ‘eval’: eval (Mul a b) = (eval a) `mul` (eval b)
? Relevant bindings include
b :: Expr (Number n)
(bound at /home/michael/git/brokensquanchy/src/Lib.hs:45:13)
a :: Expr (Number n)
(bound at /home/michael/git/brokensquanchy/src/Lib.hs:45:11)
|
45 | eval (Mul a b) = (eval a) `mul` (eval b)
| ^^^^^^^^^^^^^^^^^^^^^^^
Failed, one module loaded.
但是,當我在 ghci 中手動嘗試以下操作時,它可以作業
(eval (NumberConst (NumInt 6))) `mul` (eval (NumberConst (NumInt 2)))
12
這讓我相信我沒有在我的 eval 型別簽名中提供足夠的細節。解決方案是什么?
編輯 - 我認為我沒有正確解決這個問題或清楚地解釋它。
解決luqui的問題
您已經用
a- 您可以從型別推斷出它將是哪個建構式 - 建構式對您沒有任何作用。為什么不Number a從這個源檔案中洗掉,只是用a到處替換它?
好的,讓我們這樣做 NumberConst
data Expr a where
NumberConst :: a -> Expr a
現在我可以做到這一點
:t NumberConst ("bad code")
NumberConst ("bad code") :: Expr [Char]
I don't want to be able to do that.
So, I need a way to (1) enforce that my arithmetic constructors can only have an Int or a Float (2) avoid duplicate constructors, one for both Int and Float. I should be able to use arithmetic constructors on either Int or Float. (3) be able to know if it's an Int or a Float so that I can handle division.
A data family plus type class is the way I thought to be able to accomplish that. If that's not necessary, I'm all for a simpler approach. I just don't know what that is.
uj5u.com熱心網友回復:
好的,讓我們用 NumberConst 來做吧
現在我可以做到這一點data Expr a where NumberConst :: a -> Expr a我不想那樣做。:t NumberConst ("bad code") NumberConst ("bad code") :: Expr [Char]
我知道了。在這里,我們遇到了一個哲學問題:強型別有什么意義?許多程式員會說,要拒絕格式錯誤的程式。這當然是一個優勢,但 Haskell 長期以來一直遵循 Simon Peyton Jones 所稱的性感型別的方向:型別簽名不應該只是禁止某些事情,而是引導您,以便程式自動正確。
換句話說,NumberConst僅僅為了預防而限制可以包含的型別NumberConst "bad code"并不性感。相反,您應該只需要實際需要的屬性。這不需要任何資料系列,只需要一個型別類。看來你基本上只需要Num/ Fractional,除了一個除法是div整數和/浮點型別的兩倍。(順便說一句,這可能不是一個好主意,但讓我們繼續吧。)
class Num a => Divisible a where
divide :: a -> a -> a
instance Divisible Int where divide = div
instance Divisible Float where divide = (/)
data Expr a where
NumberConst :: Divisible a => a -> Expr a
Mul :: Expr n -> Expr n -> Expr n
這個約束足以證明每個非無限都Expr a滿足Divisible a。當您實施時,eval您會發現 GHC 拒絕承認這一點。作為一種解決方案,您可以簡單地將約束也添加到Mul
Mul :: Divisible n => Expr n -> Expr n -> Expr n
...嚴格來說這是多余的,但這可能無關緊要;或者您可以實作一個從運算式中提取證明的持續傳遞樣式的輔助函式:
{-# LANGUAGE RankNTypes #-}
evalCPS :: Expr a -> (Divisible a => a -> r) -> r
evalCPS (NumberConst n) q = q n
evalCPS (Mul x y) q = x `evalCPS` \x' -> q $ x' * eval y
eval :: Expr a -> a
eval = evalCPS id
(3)能夠知道它是一個
Int還是一個,Float以便我可以處理除法。
上述解決方案沒有做到這一點。事實上,任何人都可以稍后添加更多實體。如果你不希望出現這種情況,如果你真的想只允許Int和Float準確,希望能夠找出哪一個是的話,我建議你不要使用單獨的建構式,但只適用于常量:
data Expr a where
IntConst :: Int -> Expr Int
FloatConst :: Float -> Expr Float
Mul :: Expr a -> Expr a -> Expr a
現在,您可以再次使用eval允許不同延續的 CPS 版本,具體取決于它是Int還是Float:
evalCPSMono :: Expr a -> (Int -> r) -> (Float -> r) -> r
evalCPSMono (IntConst i) qI _ = qI i
evalCPSMono (FloatConst w) _ qF = qF w
evalCPSMono (Mul x y) qI qF
= evalCPSMono x (\x' -> qI $ x' * eval y)
(\x' -> qF $ x' * eval y)
或者,您可以將限制表達為具有封閉型別 family 的兩種型別。
type NumberAllower a where
NumberAllower Int = Int
NumberAllower Float = Float
NumberAllower a = Void
data Expr a where
NumberConst :: NumberAllower a -> Expr a
這可以防止任何人寫作NumberConst "bad code"。但它沒有做的是讓您找出Expr. 無論如何,您最終不得不從外部手動要求。不性感。
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/406841.html
標籤:
