体验在生产中使用索引monad的报告? [英] Experience reports using indexed monads in production?

查看:101
本文介绍了体验在生产中使用索引monad的报告?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在之前的问题中,我发现了Conor McBride的 Kleisli箭的存在的疯狂财富,同时寻找编码方式伊德里斯的例子哈斯克尔。我努力理解McBride的代码并在Haskell中进行编译,导致了这一要求: https://gist.github .com / abailly / 02dcc04b23d4c607f33dca20021bcd2f

在Hackage上搜索时,我发现了这些概念的几个实现,特别是(猜猜谁?) Edward Kmett Gabriel Gonzalez

有什么经验可以让这些代码投入生产?特别是,预期的好处(运行安全,自我指导使用)是否真的发生IRL?



编辑:我将标题更改为更清晰的关于我在寻找的内容用于:在野外实际使用索引单子。我有兴趣使用它们,并且我有几个使用案例,只是想知道其他人是否已经在生产代码中使用过它们。



编辑2 :由于迄今为止提供的很好的答案和有用的评论,我再次编辑了该问题的标题和描述,以更准确地反映我期望的答案,例如体验报告。

解决方案

我认为下面应该算作一个实际的例子:在编译器中静态执行良好堆栈。第一个样板:

  { - #LANGUAGE GADTs,KindSignatures# - } 
{ - #LANGUAGE DataKinds,TypeOperators# - }
{ - #LANGUAGE RebindableSyntax# - }

导入合格前奏曲
导入前导隐藏(返回,失败,(>> =),(>>) )

然后使用一个简单的堆栈语言:

<$ Op(i :: [*])(j :: [*])其中
IMM :: a - > Op i(a':i)
BINOP ::(a - > b - > c) - > Op(a':b':i)(c':i)
BRANCH :: Label i j - >标签i j - > Op(Bool':i)j

我们不会打扰真正的标签 s:

  data标签(i :: [*])(j :: [ *])其中
Label :: Prog ij - >标签ij

Prog rams只是类型 Op s:

  data Prog(i :: [*])(j :: [*])其中
PNil :: Prog ii
PCons :: Op ij - > Prog j k - >因此,在这个设置中,我们可以非常容易地创建一个编译器,它是一个索引编写器monad;也就是一个索引monad:

  class IMonad(m :: idx  - > idx  - > *  - > * )其中
ireturn :: a - > m i我a a
ibind :: m i j a - > (a - > m j k b) - > m i k b

- 对于RebindableSyntax,这样我们就可以得到那个可爱的'do'sugar
return ::(IMonad m)=> a - > m i a a
return = ireturn
(>> =)::(IMonad m)=> m i j a - > (a - > m j k b) - > m i k b
(>> =)= ibind
m>> n = m>> =常数n
失败=错误

(n索引)monoid:

  class IMonoid(m :: idx  - > idx  - > *)其中
imempty :: mii
imappend :: mij - > m j k - > mik

就像普通的 Writer



  newtype IWriter w(i :: [*])(j :: [*])(a :: *)= IWriter { runIWriter ::(wij,a)} 

实例(IMonoid w)=> IMonad(IWriter w)其中
ireturn x = IWriter(imempty,x)
ibind m f = IWriter $ case runIWriter m of
(w,x) - >
(w',y) - >的情况runIWriter(f x) (w`imappend` w',y)

itell :: w i j - > IWriter wij()
itell w = IWriter(w,())

将此机制应用于 Prog rams:

 实例IMonoid Prog其中
imempty = PNil
imappend PNil prog'= prog'
imappend(PCons op prog)prog'= PCons op $ imappend prog prog'

类型编译器= IWriter Prog

tellOp :: Op ij - >编译器i j()
tellOp op = itell $ PCons op PNil

label :: Compiler i j() - >编译器k k(Label i j)
label m = case runIWriter m of
(prog,()) - > ireturn(Label prog)

然后我们可以尝试编译一个简单的表达式语言:

  data Expr a where 
Lit :: a - > Expr a
And :: Expr Bool - > Expr Bool - > Expr Bool
Plus :: Expr Int - > Expr Int - > Expr Int
如果:: Expr Bool - > Expr a - > Expr a - > Expr a

compile :: Expr a - >编译器i(a':i)()
compile(Lit x)= tellOp $ IMM x
compile(And xy)= do
compile x
compile y
tellOp $ BINOP(&& amp;)
编译(加xy)=做
编译x
编译y
tellOp $ BINOP(+)
compile(If bte)= do
labThen< - label $ compile t
labElse< - label $ compile e
compile b
tellOp $ BRANCH labThen labElse

,如果我们省略了例如其中一个参数 BINOP ,类型检测器会检测到这个:

  compile(And xy)= do 
compile x
tellOp $ BINOP(&&)





  • 无法从上下文推导出: i〜(Bool:i)
    a〜Bool



In a previous question I discovered the existence of Conor McBride's Kleisli arrows of Outrageous Fortune while looking for ways of encoding Idris examples in Haskell. My efforts to understand McBride's code and make it compile in Haskell led to this gist: https://gist.github.com/abailly/02dcc04b23d4c607f33dca20021bcd2f

While searching on Hackage, I discovered several implementations of these concepts, notably by (guess who?) Edward Kmett and Gabriel Gonzalez.

What experience do people have putting such code in production? In particular, do the expected benefits (runtime safety, self-guiding usage) actually occur IRL? How about maintaining this kind of code over time and onboarding newcomers?

EDIT: I changed the title to be more explicit about what I am looking for: Real-world use of indexed monads in the wild. I am interested in using them and I have several use-cases in mind, just wondering if other people have already used them in "production" code.

EDIT 2: Thanks to the great answers provided so far and helpful comments, I edited that question's title and description again to reflect more precisely what kind of answer I expect, e.g. experience report.

解决方案

I think the below should count as a practical example: statically enforcing "well-stackedness" in a compiler. Boilerplate first:

{-# LANGUAGE GADTs, KindSignatures #-}
{-# LANGUAGE DataKinds, TypeOperators #-}
{-# LANGUAGE RebindableSyntax #-}

import qualified Prelude
import Prelude hiding (return, fail, (>>=), (>>))

Then a simple stack language:

data Op (i :: [*]) (j :: [*]) where
    IMM :: a -> Op i (a ': i)
    BINOP :: (a -> b -> c) -> Op (a ': b ': i) (c ': i)
    BRANCH :: Label i j -> Label i j -> Op (Bool ': i) j

and we won't bother with real Labels:

data Label (i :: [*]) (j :: [*]) where
    Label :: Prog i j -> Label i j

and Programs are just type-aligned lists of Ops:

data Prog (i :: [*]) (j :: [*]) where
    PNil :: Prog i i
    PCons :: Op i j -> Prog j k -> Prog i k

So in this setting, we can very easily make a compiler which is an indexed writer monad; that is, an indexed monad:

class IMonad (m :: idx -> idx -> * -> *) where
    ireturn :: a -> m i i a
    ibind :: m i j a -> (a -> m j k b) -> m i k b

-- For RebindableSyntax, so that we get that sweet 'do' sugar
return :: (IMonad m) => a -> m i i a
return = ireturn
(>>=) :: (IMonad m) => m i j a -> (a -> m j k b) -> m i k b
(>>=) = ibind
m >> n = m >>= const n
fail = error

that allows accumulating a(n indexed) monoid:

class IMonoid (m :: idx -> idx -> *) where
    imempty :: m i i
    imappend :: m i j -> m j k -> m i k

just like regular Writer:

newtype IWriter w (i :: [*]) (j :: [*]) (a :: *) = IWriter{ runIWriter :: (w i j, a) }

instance (IMonoid w) => IMonad (IWriter w) where
    ireturn x = IWriter (imempty, x)
    ibind m f = IWriter $ case runIWriter m of
        (w, x) -> case runIWriter (f x) of
            (w', y) -> (w `imappend` w', y)

itell :: w i j -> IWriter w i j ()
itell w = IWriter (w, ())

So we just apply this machinery to Programs:

instance IMonoid Prog where
    imempty = PNil
    imappend PNil prog' = prog'
    imappend (PCons op prog) prog' = PCons op $ imappend prog prog'

type Compiler = IWriter Prog

tellOp :: Op i j -> Compiler i j ()
tellOp op = itell $ PCons op PNil

label :: Compiler i j () -> Compiler k k (Label i j)
label m = case runIWriter m of
    (prog, ()) -> ireturn (Label prog)

and then we can try compiling a simple expression language:

data Expr a where
    Lit :: a -> Expr a
    And :: Expr Bool -> Expr Bool -> Expr Bool
    Plus :: Expr Int -> Expr Int -> Expr Int
    If :: Expr Bool -> Expr a -> Expr a -> Expr a

compile :: Expr a -> Compiler i (a ': i) ()
compile (Lit x) = tellOp $ IMM x
compile (And x y) = do
    compile x
    compile y
    tellOp $ BINOP (&&)
compile (Plus x y) = do
    compile x
    compile y
    tellOp $ BINOP (+)
compile (If b t e) = do
    labThen <- label $ compile t
    labElse <- label $ compile e
    compile b
    tellOp $ BRANCH labThen labElse

and if we omitted e.g. one of the arguments to BINOP, the typechecker will detect this:

compile (And x y) = do
    compile x
    tellOp $ BINOP (&&)

  • Could not deduce: i ~ (Bool : i) from the context: a ~ Bool

这篇关于体验在生产中使用索引monad的报告?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆