GHC Haskell目前的约束系统有什么问题?
我听说Haskell的“破坏”约束系统存在一些问题,如GHC 7.6及以下。 它出什么问题了? 是否有一个可比的现有系统克服这些缺陷?
例如,edwardk和tekmo都遇到了麻烦(例如tekmo的这个评论 )。
好的,我之前和其他人进行了几次讨论,因为我希望得到这个结果。 他们都告诉我,我所描述的所有问题都归结为缺乏多态约束。
这个问题的最简单的例子是MonadPlus
类,定义为:
class MonadPlus m where mzero :: ma mplus :: ma -> ma -> ma
…遵循以下法律:
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禁止我们写这个forms的约束:
(forall a . Monoid (ma)) => ...
所以Haskell程序员必须通过定义一个单独的类来处理这个特定的多态情况,来解决types系统的这个缺陷。
但是,这并不总是一个可行的解决scheme。 例如,在我自己的pipes
库工作中,我经常遇到需要对表单进行约束:
(forall a' ab' b . Monad (paa' b' bm)) => ...
与MonadPlus
解决scheme不同,我不能将Monad
types类切换到不同的types类来解决多态约束问题,因为那样我的图书馆用户就会丢失标记,这是一个很高的代价。
这也出现在组成变压器时,包括monad变压器和我在图书馆中包含的代理变压器。 我们想写一些类似于:
data Compose t1 t2 mr = 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 (tm)) => MonadTrans t where lift :: (Monad m) => mr -> tmr
…但是Haskell的约束系统不允许这样做。
随着Haskell用户移动到更高types的构造函数,这个问题会变得越来越明显。 你通常会有一个表单的types:
class SomeClass someHigherKindedTypeConstructor where ...
…但你会想约束一些较低的派生types的构造函数:
class (SomeConstraint (someHigherKindedTypeConstructor abc)) => SomeClass someHigherKindedTypeConstructor where ...
但是,没有多态约束,这个约束是不合法的。 我一直是最近抱怨这个问题的人,因为我的pipes
库使用的types非常多,所以我经常遇到这个问题。
有一些使用数据types的解决方法是几个人向我提出的,但是我还没有时间来评估它们,以了解它们需要哪些扩展或哪些扩展正确地解决了我的问题。 有人更熟悉这个问题,也许可以提供一个单独的答案,详细解决这个问题,为什么它的作品。
[加布里埃尔·冈萨雷斯回答的后续]
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> | ... ...
种类可以省略,也可以省略一类:
<simply-quantified-type-expression> ::= (<constraints-that-uses-rank-1-type-variables>) => <type-expression>
例如:
{-# LANGUAGE Rank2Types #-} msum :: forall m a. Monoid (ma) => [ma] -> ma msum = mconcat mfilter :: forall m a. (Monad m, Monoid (ma)) => (a -> Bool) -> ma -> ma mfilter p ma = do { a <- ma; if pa then return a else mempty } guard :: forall m. (Monad m, Monoid (m ())) => Bool -> m () guard True = return () guard False = mempty
或者没有Rank2Types
(因为我们在这里只有排名第一的types),并且使用CPP
(j4f):
{-# LANGUAGE CPP #-} #define MonadPlus(m, a) (Monad m, Monoid (ma)) msum :: MonadPlus(m, a) => [ma] -> ma msum = mconcat mfilter :: MonadPlus(m, a) => (a -> Bool) -> ma -> ma mfilter p ma = do { a <- ma; if pa then return a else mempty } guard :: MonadPlus(m, ()) => Bool -> m () guard True = return () guard False = mempty
“问题”是我们不能写
class (Monad m, Monoid (ma)) => MonadPlus m where ...
要么
class forall m a. (Monad m, Monoid (ma)) => MonadPlus m where ...
也就是说, forall m a. (Monad m, Monoid (ma))
可以用作独立约束,但是不能用*->*
types的新的单参数types类别进行别名。
这是因为types定义机制是这样工作的:
class (constraints[a, b, c, d, e, ...]) => ClassName (abc) (de) ...
即rhs侧引入typesvariables,而不是lhs或lhs。
相反,我们需要编写2参数types类:
{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts, FlexibleInstances #-} class (Monad m, Monoid (ma)) => MonadPlus ma where mzero :: ma mzero = mempty mplus :: ma -> ma -> ma mplus = mappend instance MonadPlus [] a instance Monoid a => MonadPlus Maybe a msum :: MonadPlus ma => [ma] -> ma msum = mconcat mfilter :: MonadPlus ma => (a -> Bool) -> ma -> ma mfilter p ma = do { a <- ma; if pa then return a else mzero } guard :: MonadPlus m () => Bool -> m () guard True = return () guard False = mzero
缺点:我们每次使用MonadPlus
都需要指定第二个参数。
问题:如何
instance Monoid a => MonadPlus Maybe a
如果MonadPlus
是单参数typestypes,可以写入? 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)
如果我们需要*
types,(7 – 2 * 1) – 参数typestypes为* -> *
types, 7 – 2 * 0)为* -> * -> *
types。