如何理解下面這段Haskell代碼?
初學Haskell,有關自定義類型的部分感到很困惑。
在作業中遇到下面這段代碼:
class R r where
sum :: Integer -&> r
instance R Integer where
sum x = x
instance (Integral a, R r) =&> R (a -&> r) where
sum x y = sum (x + toInteger y)
該程序用於整數求和,可以接受任意多個參數。
注意到其中
instance (Integral a, R r) =&> R (a -&> r) where
這裡a被限定為Integral,然而a在後面的sum函數體中僅被用於作為toInteger的參數。所以我想,如果直接把a替換為Integer,應該也可以正常執行。改動後:
instance (R r) =&> R (Integer -&> r) where
編譯通過,但給出參數執行時報錯了。
請問這是為什麼?
import Prelude hiding (sum)
class R r where
sum :: Integer -&> r
instance R Integer where
sum x = x
instance (Integral a, R r) =&> R (a -&> r) where
sum x y = sum (x + toInteger y)
*Main&> sum 1 2 3 4 5 6:: Integer
21
instance (R r) =&> R (Integer -&> r) where
錯誤提示已經很清楚了。
No instance for (Num a1) arising from the literal `3"
The type variable `a1" is ambiguous
Note: there are several potential instances:
instance Integral a =&> Num (GHC.Real.Ratio a)
-- Defined in `GHC.Real"
instance Num Integer -- Defined in `GHC.Num"
instance Num Double -- Defined in `GHC.Float"
...plus three others
In the third argument of `sum", namely `3"
In the expression: sum 1 2 3 :: Integer
In an equation for `it": it = sum 1 2 3 :: Integer
造成這一問題的原因是數字的值,也就是1、2、3被重載了,它們的類型是Num a =&> a,所以可能有多個潛在的instance。看一下數字類型類關係圖:
可以看到如果類型系統可以自動推斷出Integer,那麼就需要找一條路徑從Num到Integral下面的Integer類型,可是這種路徑不一定是唯一的,如果不是用哪條呢?即便這裡是唯一的,但編譯器怎麼知道!?那麼就需要我們加入類型上下文來指定。可以見視頻https://www.youtube.com/watch?v=hIZxTQP1ifo或者知乎問題Edward Kmett 的這個講座在講什麼? - 知乎用戶的回答。and True False True False :: Bool
則不會出現這種問題
{-# LANGUAGE FlexibleInstances #-}
import Prelude hiding (and)
class T r where
and :: Bool -&> r
instance T Bool where
and x = x
instance T r =&> T (Bool -&> r) where
and x y = and (x y)
我稍微修改了下你的代碼,增加了FlexibleInstances的ghc擴展,代碼具體如下:
{-# LANGUAGE FlexibleInstances #-}
import Prelude hiding (sum)
class R r where
sum :: Integer -&> r
instance R Integer where
sum x = x
{-
instance (Integral a, R r) =&> R (a -&> r) where
sum x y = sum (x + toInteger y)
-}
instance R r =&> R (Integer -&> r) where
-- sum x y = sum (x + toInteger y)
sum x = y -&> sum (x + y)
*Main&> :l class-sum.hs
[1 of 1] Compiling Main ( class-sum.hs, interpreted )
Ok, modules loaded: Main.
*Main&>
*Main&> :t sum (1::Integer) (2::Integer)
sum (1::Integer) (2::Integer) :: R t =&> t
*Main&> sum (1::Integer) (2::Integer)
3
*Main&> :t sum 1 2
sum 1 2 :: (Num a, R (a -&> t)) =&> t
*Main&> sum 1 2
& 這個問題主要是檢測遞歸數據類型的功能,instance R r =&> R (Integer -&> r)是一個遞歸類型了。
Non type-variable argument in the constraint: R (a -&> t)
(Use FlexibleContexts to permit this)
When checking that 『it』 has the inferred type
it :: forall a t. (Num a, R (a -&> t)) =&> t
在GHC 7.10.2下你改動後的代碼是無法通過編譯的:
Illegal instance declaration for 『R (Integer -&> r)』
(All instance types must be of the form (T a1 ... an) where a1 ... an are *distinct type variables*, and each type variable appears at most once in the instance head. Use FlexibleInstances if you want to disable this.) In the instance declaration for 『R (Integer -&> r)』
至於為什麼要開FlexibleInstance才能編譯我還沒搞明白,估計和instance R [char]這樣的原因類似吧。。。
不過開了的話運行時還是會有問題。。。除非你用一種非常鬼畜的寫法:
sum 1 (2::Integer) (3::Integer)
你倒是看看編譯錯誤提示啊
Illegal instance declaration for 『R (R r =&> Integer -&> r)』
(All instance types must be of the form (T a1 ... an)
where a1 ... an are *distinct type variables*,
and each type variable appears at most once in the instance head.
Use FlexibleInstances if you want to disable this.)
In the instance declaration for 『R ((R r) =&> Integer -&> r)』
All instance types must be of the form (T a1 ... an) where a1 ... an are *distinct type variables*
Integer 不是 type variable
其實你的想法是對的,只是編譯器預設不支持而已。 FlexibleInstances flag可以支持這種寫法。推薦閱讀:
※參加 2017 年函數式編程聚會是什麼感受?
※微軟和 Haskell 之間有什麼關係?
※函數式編程所倡導使用的「不可變數據結構」如何保證性能?
※函數式編程語言該如何表示樹結構呢?
※每一個 Haskell 中的「範疇論的」概念都可以去 co 嗎?