我听说Haskell的“破损”约束系统存在一些问题,如GHC 7.6及以下版本。它出什么问题了?是否有可比的现有系统克服了这些缺陷?
例如,edwardk和tekmo都遇到了麻烦(例如this comment from tekmo)。
答案 0 :(得分:22)
好的,我在发布之前与其他人进行了几次讨论,因为我想要做到这一点。他们都告诉我,我描述的所有问题都归结为缺乏多态约束。
此问题的最简单示例是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
库使用的类型非常多,所以我经常遇到这个问题。
有一些变通方法使用了几个人向我提出的数据类型,但我还没有时间对它们进行评估,以了解它们需要哪些扩展或哪一个正确地解决了我的问题。更熟悉这个问题的人可能会提供一个单独的答案,详细说明解决方案及其工作原理。
答案 1 :(得分:12)
[加布里埃尔·冈萨雷斯回答的后续行动]
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
(因为我们这里只有排名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 (m a))
可以用作独立约束,但不能使用*->*
类型的新的单参数类型类别别名。
这是因为类型类定义机制的工作原理如下:
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
的{{1}}:
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 m a b n c d e. (Foo (m a b), Bar (n c d) e)
类型,({7 - 2 * 1) - *
类型的参数类型类,则会产生(7 - 2 * 2) - 参数类型类,和* -> *
类型的(7 - 2 * 0)。