Аппликативный экземпляр для (Monad m, Monoid o) => m o?

Извините за ужасное название. Я пытаюсь создать экземпляр Applicative для Monad, обертывающего тип, который является Monoid.

instance (Monad m, Monoid o) => Applicative (m o) where
    pure x = return mempty
    xm <*> ym = do
        x <- xm
        y <- ym
        return $ x `mappend` y

Это не работает; GCHi жалуется на:

Kind mis-match
The first argument of `Applicative' should have kind `* -> *',
but `m o' has kind `*'
In the instance declaration for `Applicative (m o)'

Я понимаю, что то, что я написал выше, может быть бессмысленным. Вот контекст: я пытаюсь использовать абстракцию compos, как описано в статье < em>Шаблон для почти композиционных функций. Взяв это дерево (используя версию compos GADT; я ее сильно упростил):

data Tree :: * -> * where
    Var :: String -> Expr
    Abs :: [String] -> Expr -> Expr
    App :: Expr -> [Expr] -> Expr

class Compos t where
    compos :: Applicative f => (forall a. t a -> f (t a)) -> t c  -> f (t c)

instance Compos Tree where
    compos f t =
        case t of
            Abs ps e -> pure Abs <*> pure ps <*> f e
            App e es -> pure App <*> f e <*> traverse f es
            _ -> pure t

Я собираюсь написать много функций, которые спускаются по дереву и возвращают список скажем ошибок или набор строк, а также требуют состояния по мере его опускания (например, среды привязки), например:

composFoldM :: (Compos t, Monad m, Monoid o) => (forall a. t a -> m o) -> t c -> m o
composFoldM f = ??? 

checkNames :: (Tree a) -> State (Set Name) [Error]
checkNames e =
    case e of
        Var n -> do
            env <- get
            -- check that n is in the current environment
            return $ if Set.member n env then [] else [NameError n]
        Abs ps e' -> do
            env <- get
            -- add the abstractions to the current environment
            put $ insertManySet ps env
            checkNames e'
        _ -> composFoldM checkNames e

data Error = NameError Name
insertManySet xs s = Set.union s (Set.fromList xs)

Я думаю, что все это можно абстрагировать, заставив composFoldM использовать compos для структуры (Monad m, Monoid o) => m o. Таким образом, чтобы использовать его с версией compos GADT Applicative, найденной на странице 575/576 бумага. Думаю, мне нужно сделать Applicative экземпляр этой структуры. Как бы я это сделал? Или я иду совершенно неверным путем?


person Callum Rogers    schedule 17.08.2013    source источник


Ответы (1)


Вам нужен аппликатив Constant из Data.Functor.Constant в пакете transformers, который вы можете найти здесь.

Этот Applicative имеет следующий экземпляр:

instance (Monoid a) => Applicative (Constant a) where
    pure _ = Constant mempty
    Constant x <*> Constant y = Constant (x `mappend` y)

Затем вы можете составить Constant с любым другим аппликативом, используя Compose из Data.Functor.Compose (также в пакете transformers), который вы можете найти здесь.

Compose имеет этот экземпляр Applicative:

instance (Applicative f, Applicative g) => Applicative (Compose f g) where
    pure x = Compose (pure (pure x))
    Compose f <*> Compose x = Compose ((<*>) <$> f <*> x)

Затем вы можете Compose аппликатив Constant использовать с любым другим Applicative (например, State), чтобы сохранить как некоторое состояние, так и текущее количество Monoid.

В более общем плане вам следует прочитать статью The Essence of the Iterator Pattern, в которой рассматривает эти закономерности более подробно.

person Gabriel Gonzalez    schedule 18.08.2013
comment
Похоже, это то, что мне нужно! Но как мне на самом деле его использовать? Я пытался делать что-то вроде composFoldM f = getCompose . compos (Compose . WrapMonad . Const . f), но это не работает. Есть ли примеры/объяснения того, как комбинировать функторы? - person Callum Rogers; 18.08.2013
comment
Мой Бог. Я, наконец, разработал его путем проб и улучшений. Я думаю, так вы учитесь! Правильно composFoldM f = liftM getConst . unwrapMonad . getCompose . compos (Compose . WrapMonad . liftM Const . f). :D - person Callum Rogers; 18.08.2013
comment
@CallumRogers Совершенно верно! Это одна из приятных особенностей Haskell: средство проверки типов всегда направит вас к правильному решению. - person Gabriel Gonzalez; 18.08.2013
comment
Действительно, одна из моих любимых вещей в Haskell. - person Callum Rogers; 19.08.2013
comment
Constant также существует как Const в Control.Applicative. Какой из них следует использовать? - person ipsec; 19.08.2013
comment
@ipsec Я не совсем уверен. Если бы мне пришлось выбирать, то, вероятно, Const, так как `Control.Applicative` находится в base. - person Gabriel Gonzalez; 19.08.2013
comment
Я использовал Const как часть базовой библиотеки. У них точно такая же семантика. - person Callum Rogers; 20.08.2013