如何理解下面這段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 的這個講座在講什麼? - 知乎用戶的回答。

那麼現在問題已經很清楚了,1、2、3、1432這些數字被重載了,如果你用Bool類型寫

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)

在ghc 7.10.1的ghci測試通過,具體運行結果如下:

*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

當不指定sum的參數類型時,將會出錯,具體出錯情況如下:

*Main&> :t sum 1 2
sum 1 2 :: (Num a, R (a -&> t)) =&> t
*Main&> sum 1 2

&:12:1:
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

這個問題主要是檢測遞歸數據類型的功能,instance R r =&> R (Integer -&> r)是一個遞歸類型了。

你將ghc升級到ghc 7.10.1試試看。


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

已在StackOverflow上找到答案,鏈接在此:

recursion - Haskell, polyvariadic function and type inference


你倒是看看編譯錯誤提示啊

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 嗎?

TAG:函數式編程 | Haskell |