定义应用程序实例时出现问题

时间:2016-06-16 13:36:05

标签: haskell dependent-type type-level-computation

假设我想要定义由两个类型级别环境索引的数据类型。类似的东西:

data Woo s a = Woo a | Waa s a

data Foo (s :: *) (env :: [(Symbol,*)]) (env' :: [(Symbol,*)]) (a :: *) =
     Foo { runFoo :: s -> Sing env -> (Woo s a, Sing env') }

这个想法是env是输入环境,env'是输出环境。因此,键入Foo就像indexed state monad一样。到现在为止还挺好。我的问题是如何证明Foo是一个应用函子。显而易见的尝试是

instance Applicative (Foo s env env') where
    pure x = Foo (\s env -> (Woo x, env))
    -- definition of (<*>) omitted.

但是GHC抱怨pure是错误类型的,因为它推断出类型

    pure :: a -> Foo s env env a

而不是预期的类型

    pure :: a -> Foo s env env' a
什么是完全合理的。我的观点是,可以为Applicative定义允许更改环境类型的Foo实例吗?我用Google搜索indexed functors,但乍一看,他们似乎无法解决我的问题。有人可以建议实现这个目标吗?

2 个答案:

答案 0 :(得分:5)

您的Foo类型是Atkey最初称为parameterised monad的示例,其他所有人(可以说是错误的)现在称为索引的monad

索引monad是类似monad的东西,有两个索引,用于描述通过类型的有向图的路径。排序索引的monadic计算要求两个计算的索引像多米诺一样排列。

class IFunctor f where
    imap :: (a -> b) -> f x y a -> f x y b

class IFunctor f => IApplicative f where
    ipure :: a -> f x x a
    (<**>) :: f x y (a -> b) -> f y z a -> f x z b

class IApplicative m => IMonad m where
    (>>>=) :: m x y a -> (a -> m y z b) -> m x z b

如果您有一个描述从xy的路径的索引monad,以及从yz的方法,则索引绑定{{1会给你一个更大的计算,从>>>=x

另请注意,z会返回ipuref x x a返回的值不会在类型的有向图中执行任何步骤。就像类型级ipure

您在问题中提到的索引monad的一个简单示例是索引状态monad id,它将其参数的类型从newtype IState i o a = IState (i -> (o, a))转换为i。如果第一个的输出类型与第二个的输入类型匹配,则只能对有状态计算进行排序。

o

现在,问你的实际问题。具有多米诺式排序的newtype IState i o a = IState { runIState :: i -> (o, a) } instance IFunctor IState where imap f s = IState $ \i -> let (o, x) = runIState s i in (o, f x) instance IApplicative IState where ipure x = IState $ \s -> (s, x) sf <**> sx = IState $ \i -> let (s, f) = runIState sf i (o, x) = runIState sx s in (o, f x) instance IMonad IState where s >>>= f = IState $ \i -> let (t, x) = runIState s i in runIState (f x) t 对于转换类型级环境的计算是一个很好的抽象:您希望第一次计算使环境处于对第二种环境适合的状态。让我们为IMonad编写IMonad的实例。

我首先要注意您的Foo类型与Woo s a是同构的,这是Writer monad的一个示例。我之所以提到这一点是因为我们以后需要一个(a, Maybe s)的实例而且我懒得自己编写。

Monad (Woo s)

我已选择First作为type Woo s a = Writer (First s) a 幺半群的首选风格,但我并不确切知道您打算如何使用Maybe。您可能更喜欢Last

我也很快会利用WooTraversable的一个实例的事实。事实上,Writer比这更可行:因为它只包含一个Writer,我们不需要将任何结果粉碎在一起。这意味着我们只需要有效a的{​​{1}}约束。

Functor

让我们开始做生意。

f-- cf. traverse :: Applicative f => (a -> f b) -> t a -> f (t b) traverseW :: Functor f => (a -> f b) -> Writer w a -> f (Writer w b) traverseW f m = let (x, w) = runWriter m in fmap (\x -> writer (x, w)) (f x) 。该实例使用了Foo s的函子:我们进入状态计算,IFunctor函数通过Writer s monad里面。

fmap

我们还需要Writer定期newtype Foo (s :: *) (env :: [(Symbol,*)]) (env' :: [(Symbol,*)]) (a :: *) = Foo { runFoo :: s -> Sing env -> (Woo s a, Sing env') } instance IFunctor (Foo s) where imap f foo = Foo $ \s env -> let (woo, env') = runFoo foo s env in (fmap f woo, env') ,以便稍后与Foo一起使用。

Functor

traverseWinstance Functor (Foo s x y) where fmap = imap 。我们必须使用Foo s的{​​{1}}实例来粉碎IApplicative。这是Writer s约束的来源。

Applicative

WooMonoid s。令我们惊讶的是,我们最终委托instance IApplicative (Foo s) where ipure x = Foo $ \s env -> (pure x, env) foo <**> bar = Foo $ \s env -> let (woof, env') = runFoo foo s env (woox, env'') = runFoo bar s env' in (woof <*> woox, env'') Foo s个实例。另请注意狡猾地使用IMonad将作者内部的Writer s提供给Kleisli箭头Monad

traverseW

附录:这张照片中缺少的是变形金刚。 Instinct告诉我你应该能够将a表达为monad变换器堆栈:

f

但索引的monad并没有一个令人信服的故事来讲述变形金刚。 instance IMonad (Foo s) where foo >>>= f = Foo $ \s env -> let (woo, env') = runFoo foo s env (woowoo, env'') = runFoo (traverseW f woo) s env' in (join woowoo, env'') 的类型需要堆栈中的所有索引monad以相同的方式操作它们的索引,这可能不是你想要的。索引的monad也不能很好地与常规monad组合。

这就是说索引的monad变换器用McBride-style indexing scheme表现得更好。麦克布赖德的Foo看起来像这样:

type Foo s env env' = ReaderT s (IStateT (Sing env) (Sing env') (WriterT (First s) Identity))

然后monad变形金刚看起来像这样:

>>>=

答案 1 :(得分:1)

基本上,您在Sing env'上缺少约束 - 即它必须是Monoid,因为:

  • 您需要能够从零开始生成Sing env'类型的值(例如mempty
  • 您需要能够在Sing env'期间将<*>类型的两个值合并为一个(例如mappend)。

您还需要s中的<*>值组合,因此,除非您想从某个地方导入SemiGroup,否则您可能希望也是Monoid

{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE DeriveFunctor #-}
module SO37860911 where
import GHC.TypeLits (Symbol)
import Data.Singletons (Sing)

data Woo s a = Woo a | Waa s a 
  deriving Functor

instance Monoid s => Applicative (Woo s) where
  pure = Woo 
  Woo f <*> Woo a = Woo $ f a 
  Waa s f <*> Woo a = Waa s $ f a 
  Woo f <*> Waa s a = Waa s $ f a 
  Waa s f <*> Waa s' a = Waa (mappend s s') $ f a 

data Foo (s :: *) (env :: [(Symbol,*)]) (env' :: [(Symbol,*)]) (a :: *) =
     Foo { runFoo :: s -> Sing env -> (Woo s a, Sing env') }
  deriving Functor

instance (Monoid s, Monoid (Sing env')) => Applicative (Foo s env env') where
  pure a = Foo $ \_s _env -> (pure a, mempty)
  Foo mf <*> Foo ma = Foo $ \s env -> case (mf s env, ma s env) of
    ((w,e), (w',e')) -> (w <*> w', e `mappend` e')