擴展我的評論,這是第一個裂縫。模數是按類型強制執行的,但不是代表性的典型選擇:這只是通過計算完成的,所以需要一個抽象障礙。有限數字的類型也是可用的,但他們需要更多的工作。
Enter,{-# LANGUAGE KitchenSink #-}
。我的意思是(實際上不是太差)
{-# LANGUAGE DataKinds, GADTs, KindSignatures, FlexibleInstances #-}
讓我們開始吧。
首先,僅僅通過神經反射,我介紹Hasochistic自然數:
data Nat = Z | S Nat -- type-level numbers
data Natty :: Nat -> * where -- value-level representation of Nat
Zy :: Natty Z
Sy :: Natty n -> Natty (S n)
class NATTY n where -- value-level representability
natty :: Natty n
instance NATTY Z where
natty = Zy
instance NATTY n => NATTY (S n) where
natty = Sy natty
在我看來,這是你怎麼做,當你要聲明一個數據類型,然後允許其他類型取決於它的價值觀。理查德艾森伯格的「單身人士」圖書館自動化施工。
(如果示例繼續使用數字索引向量,有些人指出的()
載體還可以作爲單身的Nat
。他們是技術上是正確的,當然,但錯誤的。當我們想到Natty
和NATTY
從Nat
系統地生成,它們是我們可以利用或不是我們認爲合適的權利,而不是一個額外的證明。這個例子不涉及向量,並且引入向量只是爲了擁有單例對於Nat
)
我手卷了一堆轉換函數和Show
實例,所以我們可以看到我們在做什麼克,除了別的。
int :: Nat -> Integer
int Z = 0
int (S n) = 1 + int n
instance Show Nat where
show = show . int
nat :: Natty n -> Nat
nat Zy = Z
nat (Sy n) = S (nat n)
instance Show (Natty n) where
show = show . nat
現在我們準備宣佈Mod
。
data Mod :: Nat -> * where
(:%) :: Integer -> Natty n -> Mod (S n)
該類型攜帶模數。這些值帶有等值類的非標準化代表,但我們最好弄清楚如何標準化它。一元數字的劃分是我從小學到的一項奇特的運動。
remainder :: Natty n -- predecessor of modulus
-> Integer -- any representative
-> Integer -- canonical representative
-- if candidate negative, add the modulus
remainder n x | x < 0 = remainder n (int (nat (Sy n)) + x)
-- otherwise get dividing
remainder n x = go (Sy n) x x where
go :: Natty m -- divisor countdown (initially the modulus)
-> Integer -- our current guess at the representative
-> Integer -- dividend countdown
-> Integer -- the canonical representative
-- when we run out of dividend the guessed representative is canonical
go _ c 0 = c
-- when we run out of divisor but not dividend,
-- the current dividend countdown is a better guess at the rep,
-- but perhaps still too big, so start again, counting down
-- from the modulus (conveniently still in scope)
go Zy _ y = go (Sy n) y y
-- otherwise, decrement both countdowns
go (Sy m) c y = go m c (y - 1)
現在我們可以做出一個聰明的構造函數。
rep :: NATTY n -- we pluck the modulus rep from thin air
=> Integer -> Mod (S n) -- when we see the modulus we want
rep x = remainder n x :% n where n = natty
然後是Monoid
實例很容易:
instance NATTY n => Monoid (Mod (S n)) where
mempty = rep 0
mappend (x :% _) (y :% _) = rep (x + y)
我在一些其他的東西卡住,太:
instance Show (Mod n) where
show (x :% n) = concat ["(", show (remainder n x), " :% ", show (Sy n), ")"]
instance Eq (Mod n) where
(x :% n) == (y :% _) = remainder n x == remainder n y
隨着一點點的方便......
type Four = S (S (S (S Z)))
我們得到
> foldMap rep [1..5] :: Mod Four
(3 :% 4)
所以是的,你確實需要依賴類型,但Haskell依賴於足夠的類型。
Kitchen-sink Haskell確實具有足夠的依賴類型來保持模數作爲類型級數,然後爲每個正模數添加一個monoid。當然,如果你想按部門對代表進行標準化,你需要確保你保留了一個模數值的副本。 – pigworker
這個特定目的的依賴類型的替代方法是'reflection'包。您將在「Reifies s Natural」環境下工作,其中圍繞具有's'幻像的Integer的新類型將具有所有期望的實例。 'reify'會將模量投入空中,而'reflect'會將空氣拉出。 – dfeuer