假设我想要定义由两个类型级别环境索引的数据类型。类似的东西:
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,但乍一看,他们似乎无法解决我的问题。有人可以建议实现这个目标吗?
答案 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
如果您有一个描述从x
到y
的路径的索引monad,以及从y
到z
的方法,则索引绑定{{1会给你一个更大的计算,从>>>=
到x
。
另请注意,z
会返回ipure
。 f 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
。
我也很快会利用Woo
是Traversable
的一个实例的事实。事实上,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
traverseW
是instance Functor (Foo s x y) where
fmap = imap
。我们必须使用Foo s
的{{1}}实例来粉碎IApplicative
。这是Writer s
约束的来源。
Applicative
Woo
是Monoid 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')