在Monte编程语言及其祖先E中,存在用于单次定界延续的语法,称为“喷射器”,它们是在句法边界内有效地仅可使用一次的延续。例如,这里有一个没有调用的喷射器:
escape ej { 42 }
还有一个喷射器,称为:
escape ej { ej(42); "After 10,000 years, I'm free!" }
两者都评估为42
。我们还可以在调用喷射器的情况下附加一个动作:
escape ej { ej(2) } catch result { result + 4 }
这就是所有这些语法。不难想象这如何模仿Maybe
或Either
。我将把Haskell Wiki on Maybe 上的例子转录成惯用的 Monte:
def f(x, ej):
return if (x == 0) { ej() } else { x }
def g(x, ej):
return if (x == 100) { ej() } else { x }
def h(x, ej):
return g(f(x, ej), ej)
# Monte permits Unicode identifiers but not for free.
def ::"h´"(x, ej):
def n := f(x, ej)
return g(n, ej)
(注意我们必须承担传递的负担ej
。Monte 缺少 do-notation 的“可编程分号”。)
我不会Either
,但大体上是一样的;添加catch
子句的能力提供了所需的类型区分。分隔延续是众所周知的组合,因此可以构建复杂的工具:
def trying(f, g, ej):
return escape innerEj { f(innerEj) } catch _ { g(ej) }
例如,这些小工具在 Monte 中用于构建手写解析器组合器。因此,在《所有单子之母》中,丹·皮波尼解释说Cont
,从某种意义上说,这是一个非常原始Monad
的元素,许多其他元素Monad
都可以在此基础上构建。我们也可以尝试在 Monte 中执行此操作。让我们使用 Moggi 的风格在基于对象的语言中编码 monad:
object identity:
to unit(x):
return x
# "bind" is a Monte keyword; we may still use it, but not for free.
# NB this is `x >>= f` in Haskell.
to "bind"(x, f):
return f(x)
让我们对绑定助手进行编码i
,以了解它的外观:
def i(m, x, ej):
return m."bind"(x, ej)
...这没有帮助。这看起来不像是好的语法。
# Haskell: runIdentity $ do { x <- return 32; return $ x // 4 }
escape ej { i(identity, i(identity, identity.unit(32), fn x { identity.unit(x // 4) }), ej) }
未来应该有很酷的机器人,而不是这个。但是,还有另一个问题。让我们编码另一个传统的Monad
,List
:
object list:
to unit(x) { return [x] }
to "bind"(action, f):
var rv := []
for x in (action) { rv += f(x) }
return rv
让我们做一个传统的笛卡尔积。首先,让我们做一个直接的计算;我们将直接使用 list monad 直接绑定,而不是传递延续:
▲> i(list, [1, 2, 3], fn x { i(list, [4, 5, 6], fn y { [x * y] }) })
Result: [4, 5, 6, 8, 10, 12, 12, 15, 18]
现在有了一个喷射器:
▲> escape ej { i(list, i(list, [1, 2, 3], fn x { i(list, [4, 5, 6], fn y { [x * y] }) }), ej) }
Result: 4
所以!这很有趣。完整的列表单子计算正在运行,但弹出器只看到列表中的第一项。我怀疑,由于喷射器是可组合的,因此有一种方法可以构建更复杂的逻辑单子,它可以更好地不计算这么多中间结果。
那么,我的问题是:当我们将Maybe
andEither
转换为惯用的 Monte 时,我们可以清楚地看到喷射器语法优雅地应用。还有哪些其他单子会有像这样有趣的单次行为?不要觉得局限于 Haskell;Monte 是无类型的,所以没有人会因为难以键入的 monad 而回避你!