haskell - ранг n ограничений?(или монадные трансформаторы и Data.Suitable) - PullRequest
7 голосов
/ 10 ноября 2011

Я пытаюсь написать что-то похожее на «типы ранга 2», но вместо этого для ограничений.(Или, может быть, неправильно полагать, что изменение -> в определении «типы ранга 2» на => имеет смысл; пожалуйста, отредактируйте вопрос, если вы придумали лучшую терминологию).

setup

Во-первых, класс типов Suitable (из Data.Suitable , база rmonad) может использоваться для обозначения типов значений, которые можно использовать.В этом вопросе я буду использовать

Suitable m a

, чтобы обозначить, что значение a может использоваться как значение для некоторых функций монады m (в частности, если m является DSL, тогда его значения обычно a, которые подходят), например

class PrintSuitable m where
    printSuitable :: Suitable m a => a -> m ()

См. верхний комментарий для RMonad [ link ] и его источник для примера того, как использоватьПодходящее.Например, можно определить Suitable m (Map a b) и напечатать количество элементов на карте.

question

goal : Теперь у меня есть монадный преобразователь MyMonadT и хотите создать MyMonadT m экземпляр PrintSuitable всякий раз, когда m является экземпляром PrintSuitable.

мотивация ограничения ранга 2 : Проблема заключается в том, что тип a вводится в отношении функции printSuitable, т.е. не появляется в подписи class.Поскольку к сигнатуре class можно добавить только ограничения (дополнительные ограничения для реализации функции instance недопустимы), имеет смысл сказать что-то обо всех a в сигнатуре класса (строка 2 ниже).

Ниже показан предполагаемый код.

instance (PrintSuitable m, MonadTrans t,
        (forall a. Suitable (t m) a => Suitable m a), -- rank 2 constraint
        ) => PrintSuitable (t m) where

    printSuitable = lift ...

-- MyMonadT doesn't change what values are Suitable, hence the rank 2 expression,
-- (forall a. Suitable (t m) a => Suitable m a) should hold true
data instance Constraints (MyMonadT m) a =
    Suitable m a => MyMonadT_Constraints
instance Suitable m a => Suitable (MyMonadT m) a where -- the important line
    constraints = MyMonadT_Constraints

instance MonadTrans MyMonadT where ...
-- now, MyMonadT m is a PrintSuitable whenever m is a PrintSuitable

-- the manual solution, without using MonadTrans, looks roughly like this
instance PrintSuitable m => PrintSuitable (t m) where
    printSuitable a = withResConstraints $ \MyMonadT_Constraints -> ...

указанное ограничение говорит, что все, что подходит в (t m), подходит в m.Но, конечно, это не действительно Хаскель;как можно закодировать функциональный эквивалент?

Заранее спасибо !!!

Ответы [ 3 ]

8 голосов
/ 10 ноября 2011

Выполнение того, что вы просили

Если вы посмотрите в моем пакете ограничений на взлом, есть

Data.Constraint.Forall

Этоможет использоваться для создания количественных ограничений, и используя inst с другими комбинаторами ограничений из пакета, и, создав вспомогательное ограничение для установки аргумента в правильное положение, вы можете напрямую кодировать то, что вы запрашиваете.

Описание механизма отражения находится в моем блоге.

http://comonad.com/reader/2011/what-constraints-entail-part-1/

http://comonad.com/reader/2011/what-constraints-entail-part-2/

Однако для этого требуется GHC.

Во многих случаях вы часто можете справиться с этим, сделав версию вашего конкретного ограничения ранга 2.

class Monoid1 m where
    mappend1 :: m a -> m a -> m a
    mempty1 :: m a

, но в вашем случае вы хотите не только ограничение ранга 2, но и имплицитное ограничение.

Используя оборудование из этого пакета, мы можем сделать

class SuitableLowering t m where
   lowerSuitability :: Suitable (t m) a :- Suitable m a

Затем вы можете использовать

instance (PrintSuitable m, SuitableLowering t m) => PrintSuitable (t m) where

и expr \\ lowerSuitability, чтобы вручную ввести в сферу действияSuitable m a экземпляр в контексте whпрежде чем вы знаете Suitable (t m) a.

Но это действительно опасный способ выражения экземпляра, потому что он исключает возможность создания чего-либо вроде (* -> *) -> * -> * экземпляраPrintSuitable любым другим способом и может помешать определению базового случая, если вы не будете осторожны!

Делайте то, что вам нужно

The правильный способ сделать это - отказаться от определения одного экземпляра, охватывающего все случаи, и вместо этого определить printSuitableDefault, который можно использовать для любого подходящего преобразователя.

Предполагая существованиеRMonadTrans, как упомянуто в ответе Даниэля

class RMonadTrans t where
    rlift :: Suitable m a => m a -> t m a

, мы можем определить:

printSuitableDefault :: (RMonadTrans t, Suitable m a) => a -> t ()
printSuitableDefault = ...

instance PrintSuitable m => PrintSuitable (Foo m) where
  printSuitable = printSuitableDefault

instance PrintSuitable m => PrintSuitable (Bar m) where
  printSuitable = printsuitableDefault

У вас вряд ли будет слишком много преобразователей rmonad, и это гарантирует, что если вы захотите сделать одинраспечатайте по-другому, у вас есть такая гибкость.

Делать то, что вам нужно, немного приятнее под компилятором передовой кромки

Под 7.3.x (текущий заголовок GHC) или позжеВы даже можете использовать новый деобъявления о сбоях, чтобы сделать это немного менее болезненным.

class RMonad m => PrintSuitable m where
   printSuitable :: a -> m ()
   default printSuitable :: (RMonadTrans t, RMonad n, Suitable n a, m ~ t n) => 
     a -> t n ()
   printSuitable = <the default lifted definition>

тогда экземпляры для каждого трансформатора могут просто выглядеть так:

instance PrintSuitable m => PrintSuitable (Foo m)
instance PrintSuitable m => PrintSuitable (Bar m)

, и вы можете определить свой хороший базовый вариант printSuitable для некоторыхЗаброшенная монада без забот о перекрытии.

1 голос
/ 10 ноября 2011

Я не думаю, что MonadTrans будет здесь полезным, поскольку lift требует, чтобы m было Monad.Вы можете работать с

class RMonadTrans t where
    rlift :: Suitable m a => m a -> t m a

instance (RMonadTrans t, Suitable m a) => Suitable (t m) a where
    constraints = ???
0 голосов
/ 10 ноября 2011

С этим связано то, что вы можете определить экземпляр:

{-# LANGUAGE FlexibleInstances, FlexibleContexts, 
UndecidableInstances, MultiParamTypeClasses #-}
module Foo where

import Control.Monad.Trans

class Suitable m a where
    foo :: m a -> Int

class PrintSuitable m where
    printSuitable :: Suitable m a => a -> m ()

instance (PrintSuitable m, MonadTrans t, Suitable (t m) a) => Suitable m a where
    foo = const 5

instance (PrintSuitable m, MonadTrans t) => PrintSuitable (t m) where
    printSuitable = undefined

Так что вам не нужно ограничение. Это поможет?

...