Mai*_*tor 2 macros monads scheme haskell functional-programming
我读过一些monads tutoriais,他们几乎提出monad是实现操作顺序所必需的.但同样可以通过以下方式实现let:
(let*
( (a 1)
(b (+ a 1))
(c (+ b 1)))
c)
Run Code Online (Sandbox Code Playgroud)
被禁止(实际上,我为了说明目的写了这个脑谜,请参阅Will Ness的评论):
((lambda (c) c)
((lambda (b) (+ b 1))
((lambda (a) (+ a 1))
1)))
Run Code Online (Sandbox Code Playgroud)
那么如果这个宏能够实现排序,那么如何实现排序需要monad呢?
monad只是一种类型定义的一组法则.以下是Scheme符号中的法律(return以及>>=monadic函数):
(>>= (return a) k) == (k a) ; Left identity
(>>= m return) == m ; Right identity
(>>= m (lambda (x) (>>= (k x) h))) == (>>= (>>= m k) h) ; Associativity
Run Code Online (Sandbox Code Playgroud)
翻译这个变得棘手,因为Scheme是动态类型的.>>=并return根据所涉及的类型有不同的行为.
一个简单的例子是Maybemonad,它在Scheme中看起来像这样:
(define (Just x)
(cons 'Just x))
(define Nothing 'Nothing)
(define return Just)
(define (>>= m f)
(if (eq? m 'Nothing)
'Nothing
(f (cdr m))))
Run Code Online (Sandbox Code Playgroud)
这可以被认为代表可能失败的计算.以下是Maybe monad的一些示例:
(>>= (Just 1) (lambda (x) (return (+ x 5)))) == '(Just . 6)
(>>= Nothing (lambda (x) (return (* x 10)))) == 'Nothing
(>>= (Just 5) (lambda (x) Nothing)) == 'Nothing
Run Code Online (Sandbox Code Playgroud)
注意如果任何子Nothing计算结果如何,整个计算结果为Nothing.这是Maybemonad 的主要特征.
另一个常见的例子是列表monad,它可能看起来像
(define (return x) (list x))
(define (>>= xs f)
(flatten (map f xs)))
Run Code Online (Sandbox Code Playgroud)
可以将此monad视为非确定性计算(即,可能具有多个可能值的计算).这里有些例子:
(>>= '(1 2 3) (lambda (x) (return (* x 10)))) == '(10 20 30)
(>>= '(5 6 7) (lambda (x) (list (* x 10) (+ x 2)))) == '(50 7 60 8 70 9)
Run Code Online (Sandbox Code Playgroud)
注意:在强烈尝试学习monad之前,我强烈建议学习仿函数(在Haskell中使用这个词的意义上)和applicative functors.这些概念相互依赖.
(为清楚起见编辑,以更直接地解决问题.)
Monads和宏是不同类型的东西.正如我将在下面展示的那样,Haskell 中Monad的Monad do
符号Identity类似于let-recdesugaring.然后我们将看到由于Haskell中的惰性评估,desugaring是如何不等同的.
如果您不想进一步阅读,摘要是宏重写语法树(在Lisps中),而在Haskell中,monad do-notation是语法糖.Monads本身只是具有相关函数字典的类型,因此Identitymonad 与orad不同,或者IO与STMor Either或不同Cont.
首先,让我们假设Lisps和Haskell具有相同的执行策略(严格,按值调用).在这个假设中,Haskell的Monad do注释和平均Lisp 之间没有太大的区别 let-rec.对于我将要展示的所有Haskell,我将在顶部有这个import语句:
import Control.Monad.Identity
Run Code Online (Sandbox Code Playgroud)
因此,请考虑以下功能:
f :: Identity Int
f = do
a <- return 1
b <- return (a + 1)
c <- return (b + 1)
return c
Run Code Online (Sandbox Code Playgroud)
我正在使用Identity monad,其行为最相似let-rec.这个do符号将会出现这种情况:
fDesugar :: Identity Int
fDesugar =
(return 1 >>= \a ->
(return (a + 1) >>= \b ->
(return (b + 1) >>= \c ->
return c)))
Run Code Online (Sandbox Code Playgroud)
这看起来像你的Lisp示例,但显然它使用的是中缀表示法,这会产生不同的参数序列.我们可以将它重写为Lisp,如:
fLisp :: Identity Int
fLisp =
((=<<) (\c -> return c)
((=<<) (\b -> return (b + 1))
((=<<) (\a -> return (a + 1))
(return 1))))
Run Code Online (Sandbox Code Playgroud)
现在,我们可以使用"运行"这个monad runIdentity.那是什么意思?为什么我们需要"运行"monad?
Monads在类型的构造函数上定义.有些教程将它们描述为burrito包装器,但我只想说monad可以在很多类型上定义 - 那些类型.monad的一个例子是我已经使用过的
Identity,另一个是monad IO.这些类型中的每一个实际上都是一种类型的值* -> *,也就是说,它采用一种类型(种类*)并返回一种新类型.所以一个单子为定义Identity,其然后可以用使用Identity Int,
Identity String,Identity Foo,等.
因为monad是按类型定义的,所以在评估某些类型时
Identity T,我们需要知道如何执行该monad.可以说,排序取决于类型.
身份是一个非常简单的构造函数和monad.构造函数是:
newtype Identity a = Identity { runIdentity :: a }
Run Code Online (Sandbox Code Playgroud)
也就是说,要构造一个类型的值Identity T,我们只需要传入一个
T.为了解决它,我们只是申请runIdentity它.那是:
makeIdentity :: a -> Identity a
makeIdentity a = Identity a
runIdentity :: Identity a -> a
runIdentity (Identity a) = a
Run Code Online (Sandbox Code Playgroud)
要理解fLisp,fDesugar或者f,我们需要知道monad 语境中的含义>>=和
return含义Identity:
instance Monad Identity where
return a = Identity a
m >>= k = k (runIdentity m)
Run Code Online (Sandbox Code Playgroud)
有了这些知识,我们可以推导出=<<身份:
k =<< m = k (runIdentity m)
Run Code Online (Sandbox Code Playgroud)
有了这个,我们可以fLisp使用我们的定义重写.在Haskell的说法中,我们使用的是等式推理.我们可以用右侧替换上面定义的左侧.因此,我们替换
(=<<) a b用a (runIdentity b),并return a具有Identity a:
fLisp' :: Identity Int
fLisp' =
((\c -> Identity c)
(runIdentity ((\b -> Identity (b + 1))
(runIdentity ((\a -> Identity (a + 1))
(runIdentity (Identity 1)))))))
Run Code Online (Sandbox Code Playgroud)
但runIdentity只是Identity从它应用的任何东西剥离.何时
runIdentity应用于表单中的某些内容:
runIdentity ((\a -> Identity (f a)) b)我们可以将其移动到其参数中:
(\a -> runIdentity (Identity a)) b,并将其减少到(\a -> f a) b或只是
f a.我们来做所有这些步骤:
fLisp'' :: Identity Int
fLisp'' =
((\c -> Identity c)
(((\b -> (b + 1))
(((\a -> (a + 1))
1)))))
Run Code Online (Sandbox Code Playgroud)
我们终于可以Identity拿出第一个函数的最后一个了,我们得到:
fLisp''' :: Identity Int
fLisp''' =
Identity
((\c -> c)
(((\b -> (b + 1))
(((\a -> (a + 1))
1)))))
Run Code Online (Sandbox Code Playgroud)
所以,显然Identity表现得一样let-rec,对吧?
单子每个类型定义的,所以Identity是很多像您的宏let-rec,但其中两个在一个关键的方式不同的是,哈斯克尔没有表现为我们承担.Haskell不执行严格的按值调用评估.
我们可以通过在Haskell中编写来证明这一点.blowUp是一种原始错误类型,在评估时会冒出异常并结束执行.
blowUp :: a
blowUp = error "I will crash Haskell!"
riskyPair :: (Int, a)
riskyPair = (5, blowUp)
fst' :: (a, b) -> a
fst' = \(a, b) -> a
five :: Int
five = fst' riskyPair
Run Code Online (Sandbox Code Playgroud)
在five评估时,结果确实是五.尽管它来自污染的价值g,但我们能够安全地评估fst' riskyPair而不会爆炸.尝试评估riskyPair,你会看到异常.
考虑以下函数g,它使用Identity monad,但它做了什么?
g :: Identity Int
g = do
a <- return 1
b <- return (a + 1)
c <- return (b + 1)
d <- return (blowUp)
return c
Run Code Online (Sandbox Code Playgroud)
奇怪的是,runIdentity g回报3.同样的runIdentity f.
Monads不是宏,甚至Identitymonad 都没有复制行为
let-rec.我在Racket中尝试了这个,我收到了一个错误.我甚至无法编译程序!定义g评估错误代码.
(define g
(letrec
( (a 1)
(b (+ a 1))
(c (+ b 1))
(d (error "I will crash Racket!"))
) c))
Run Code Online (Sandbox Code Playgroud)
在Haskell中,值在其参数中是惰性的.如果该对的第二个值从未被强制(必需),那么Haskell将不会爆炸.并且fst f执行起来是"安全的".在Lisp中,值是严格的,并且g总是会爆炸.
由于Haskell的懒惰,许多作者将手动波单调作为排序操作的唯一方式.这不是严格意义上的!(没有双关语.)
单子的话,不只是宏let-rec,因为定义
>>=和return可这样变化很大.我们可以用Eithermonad 表示潜在的失败计算.
我倾向于将monad视为排序操作的方式,但更像是用户定义的"分号"运算符.在一些monads(IO)中,绑定行为很像C或C++中的分号.在其他monad中,分号(绑定)执行其他操作,例如将错误条件链接在一起或修改控制流.
请参阅monad的此示例
以Cont供参考.请注意,在这种情况下,在Cont
monad中,可以通过do块内的语句更改执行流程.
Monad不是一个宏.Monad是两个函数的存在,它们之间具有特定的对应关系.所以它并没有消除lambda,而是指定了对它们的限制.
我不会说monads"序列"操作.它并不总是他们所做的.A (r->a)是monad in a,它当然不会"排序"任何东西.A ((a->r)->a)也是Monad a.
我建议理解monad的重点应该是理解这两个操作和法律的含义.通常教程会谈论return和>>=,但顿悟是在理解<=<,可以用来表达>>=.
在"普通"类型中,您将组合定义为:
(.) :: (b->c) -> (a->b) -> (a->c)
Run Code Online (Sandbox Code Playgroud)
然后写
f . g
Run Code Online (Sandbox Code Playgroud)
使用monad,您可以将组合定义为:
(<=<) :: (Monad m) => (b->m c) -> (a->m b) -> (a->m c)
Run Code Online (Sandbox Code Playgroud)
然后写
f <=< g
Run Code Online (Sandbox Code Playgroud)