4

do符号允许我们在没有大量嵌套的情况下表达一元代码,因此

main = getLine >>= \ a -> 
       getLine >>= \ b ->
       putStrLn (a ++ b)

可以表示为

main = do
  a <- getLine
  b <- getLine
  putStrLn (a ++ b)

但是,假设语法允许... #expression ...代表do { x <- expression; return (... x ...) }. 例如,foo = f a #(b 1) c将被脱糖为:foo = do { x <- b 1; return (f a x c) }. 那么,上面的代码可以表示为:

main = let a = #getLine in
       let b = #getLine in
       putStrLn (a ++ b)

这将被取消为:

main = do
  x <- getLine
  let a = x in
    return (do
      x' <- getLine
      let b = x' in
        return (putStrLn (a ++ b)))

那是等价的。这种语法对我很有吸引力,因为它似乎提供了与 do-notation 相同的功能,同时还允许一些较短的表达式,例如:

main = putStrLn (#(getLine) ++ #(getLine))

所以,我想知道这个提议的语法是否有任何缺陷,或者它是否确实完整并等同于 do-notation。

4

2 回答 2

11

putStrLnis already String -> IO (),所以你的脱糖... return (... return (putStrLn (a ++ b)))最终有 type IO (IO (IO ())),这可能不是你想要的:运行这个程序不会打印任何东西!

更一般地说,您的符号不能表达任何do不以return. [见德里克·埃尔金斯的评论。]

我不相信你的符号可以表达join,它可以在do没有任何附加功能的情况下表达:

join :: Monad m => m (m a) -> m a
join mx = do { x <- mx; x }

但是,您可以将fmap约束表达为Monad

fmap' :: Monad m => (a -> b) -> m a -> m b
fmap' f mx = f #mx

and >>=(以及其他所有内容)可以使用fmap'and来表达join。所以添加join会让你的符号完整,但在很多情况下仍然不方便,因为你最终需要很多joins 。

然而,如果你放弃翻译,你会得到与Idris 的 bang notationreturn非常相似的东西:

在许多情况下,使用 do-notation 会使程序不必要地冗长,尤其是在m_add上述情况下,立即使用一次值绑定。在这些情况下,我们可以使用速记版本,如下所示:

m_add : Maybe Int -> Maybe Int -> Maybe Int
m_add x y = pure (!x + !y)

该表示法!expr意味着expr应该评估表达式然后隐式绑定。从概念上讲,我们可以将其!视为具有以下类型的前缀函数:

(!) : m a -> a

但是请注意,它并不是真正的函数,只是语法!在实践中,子表达式!exprexpr在其当前范围内尽可能提升,将其绑定到新名称x,并替换!exprx。表达式首先提升深度,从左到右。在实践中,!-notation 允许我们以更直接的风格进行编程,同时仍然提供关于哪些表达式是单子的符号线索。

例如,表达式:

let y = 42 in f !(g !(print y) !x)

被提升到:

let y = 42 in do y' <- print y
                 x' <- x
                 g' <- g y' x'
                 f g'

讨论了将其添加到 GHC 中,但被拒绝(到目前为止)。不幸的是,我找不到讨论它的线程。

于 2017-04-16T06:17:34.153 回答
2

这个怎么样:

do a <- something
   b <- somethingElse a
   somethingFinal a b
于 2017-04-16T05:26:14.863 回答