[英]What's wrong with GHC Haskell's current constraint system?
我聽說Haskell的“破壞”約束系統存在一些問題,如GHC 7.6及以下版本。 它出什么問題了? 是否有可比的現有系統克服了這些缺陷?
例如,edwardk和tekmo都遇到了麻煩(例如來自tekmo的評論 )。
好的,我在發布之前與其他人進行了幾次討論,因為我想要做到這一點。 他們都告訴我,我描述的所有問題都歸結為缺乏多態約束。
這個問題最簡單的例子是MonadPlus
類,定義如下:
class MonadPlus m where
mzero :: m a
mplus :: m a -> m a -> m a
......遵守以下法律:
mzero `mplus` m = m
m `mplus` mzero = m
(m1 `mplus` m2) `mplus` m3 = m1 `mplus` (m2 `mplus` m3)
請注意,這些是Monoid
定律,其中Monoid
類由下式給出:
class Monoid a where
mempty :: a
mappend :: a -> a -> a
mempty `mplus` a = a
a `mplus` mempty = a
(a1 `mplus` a2) `mplus` a3 = a1 `mplus` (a2 `mplus` a3)
那么我們為什么要擁有MonadPlus
課程呢? 原因是因為Haskell禁止我們編寫表單的約束:
(forall a . Monoid (m a)) => ...
因此,Haskell程序員必須通過定義一個單獨的類來處理這種特定的多態情況,從而解決類型系統的這個缺陷。
但是,這並不總是可行的解決方案。 例如,在我自己的pipes
庫工作中,我經常遇到需要構造表單的約束:
(forall a' a b' b . Monad (p a a' b' b m)) => ...
不像MonadPlus
的解決方案,我不能到交換機Monad
型類不同類型的類來解決多態約束問題,因為后來我庫的用戶將失去do
記號,這是一個很高的代價。
在編寫變換器時,也會出現這種情況,包括monad變換器和我在庫中包含的代理變換器。 我們想寫一些類似的東西:
data Compose t1 t2 m r = C (t1 (t2 m) r)
instance (MonadTrans t1, MonadTrans t2) => MonadTrans (Compose t1 t2) where
lift = C . lift . lift
第一次嘗試不起作用,因為lift
不會將其結果限制為Monad
。 我們實際上需要:
class (forall m . Monad m => Monad (t m)) => MonadTrans t where
lift :: (Monad m) => m r -> t m r
......但是Haskell的約束系統不允許這樣做。
隨着Haskell用戶轉向更高類型的構造函數,這個問題將變得越來越明顯。 您通常會有一個表單類型:
class SomeClass someHigherKindedTypeConstructor where
...
...但是你想要約束一些低級的派生類型構造函數:
class (SomeConstraint (someHigherKindedTypeConstructor a b c))
=> SomeClass someHigherKindedTypeConstructor where
...
但是,如果沒有多態約束,則該約束不合法。 我最近一直抱怨這個問題,因為我的pipes
庫使用的類型非常多,所以我經常遇到這個問題。
有一些使用數據類型的解決方法,有幾個人向我提出,但我還沒有時間來評估它們以了解它們需要哪些擴展或哪一個正確地解決了我的問題。 更熟悉這個問題的人可能會提供一個單獨的答案,詳細說明解決方案及其工作原理。
[Gabriel Gonzalez回答的后續行動]
Haskell中約束和量化的正確表示法如下:
<functions-definition> ::= <functions> :: <quantified-type-expression>
<quantified-type-expression> ::= forall <type-variables-with-kinds> . (<constraints>) => <type-expression>
<type-expression> ::= <type-expression> -> <quantified-type-expression>
| ...
...
可以省略種類,以及排名1類型的forall
:
<simply-quantified-type-expression> ::= (<constraints-that-uses-rank-1-type-variables>) => <type-expression>
例如:
{-# LANGUAGE Rank2Types #-}
msum :: forall m a. Monoid (m a) => [m a] -> m a
msum = mconcat
mfilter :: forall m a. (Monad m, Monoid (m a)) => (a -> Bool) -> m a -> m a
mfilter p ma = do { a <- ma; if p a then return a else mempty }
guard :: forall m. (Monad m, Monoid (m ())) => Bool -> m ()
guard True = return ()
guard False = mempty
或者沒有Rank2Types
(因為我們這里只有rank-1類型),並且使用CPP
(j4f):
{-# LANGUAGE CPP #-}
#define MonadPlus(m, a) (Monad m, Monoid (m a))
msum :: MonadPlus(m, a) => [m a] -> m a
msum = mconcat
mfilter :: MonadPlus(m, a) => (a -> Bool) -> m a -> m a
mfilter p ma = do { a <- ma; if p a then return a else mempty }
guard :: MonadPlus(m, ()) => Bool -> m ()
guard True = return ()
guard False = mempty
“問題”是我們不能寫
class (Monad m, Monoid (m a)) => MonadPlus m where
...
要么
class forall m a. (Monad m, Monoid (m a)) => MonadPlus m where
...
也就是說, forall m a. (Monad m, Monoid (ma))
forall m a. (Monad m, Monoid (ma))
可以用作獨立約束,但不能使用*->*
類型的新的單參數類型類別別名。
這是因為類型類定義機制的工作原理如下:
class (constraints[a, b, c, d, e, ...]) => ClassName (a b c) (d e) ...
即rhs側引入類型變量,而不是lhs或lhs或forall
。
相反,我們需要編寫2參數類型類:
{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts, FlexibleInstances #-}
class (Monad m, Monoid (m a)) => MonadPlus m a where
mzero :: m a
mzero = mempty
mplus :: m a -> m a -> m a
mplus = mappend
instance MonadPlus [] a
instance Monoid a => MonadPlus Maybe a
msum :: MonadPlus m a => [m a] -> m a
msum = mconcat
mfilter :: MonadPlus m a => (a -> Bool) -> m a -> m a
mfilter p ma = do { a <- ma; if p a then return a else mzero }
guard :: MonadPlus m () => Bool -> m ()
guard True = return ()
guard False = mzero
缺點:我們每次使用MonadPlus
都需要指定第二個參數。
問題:如何
instance Monoid a => MonadPlus Maybe a
如果MonadPlus
是單參數類型類,可以寫嗎? MonadPlus Maybe
來自base
:
instance MonadPlus Maybe where
mzero = Nothing
Nothing `mplus` ys = ys
xs `mplus` _ys = xs
工作不像Monoid Maybe
:
instance Monoid a => Monoid (Maybe a) where
mempty = Nothing
Nothing `mappend` m = m
m `mappend` Nothing = m
Just m1 `mappend` Just m2 = Just (m1 `mappend` m2) -- < here
:
(Just [1,2] `mplus` Just [3,4]) `mplus` Just [5,6] => Just [1,2]
(Just [1,2] `mappend` Just [3,4]) `mappend` Just [5,6] => Just [1,2,3,4,5,6]
forall mabncd e. (Foo (mab), Bar (ncd) e)
, forall mabncd e. (Foo (mab), Bar (ncd) e)
forall mabncd e. (Foo (mab), Bar (ncd) e)
如果我們想要*
類型,(7 - 2 * 1) - 參數類型為* -> *
類型,則產生(7 - 2 * 2) - 參數類型類,( 7 - 2 * 0)表示* -> * -> *
類型。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.