没有 do 块或累积括号的惯用 Haskell 语法?

Idiomatic Haskell syntax without do-blocks or accumulating parentheses?

我是 Haskell 的新手,一直在尝试寻找一种方法将多个 IO 污染值传递给函数以处理 C 库。大多数人似乎在 do 块中使用 <- 运算符,如下所示:

g x y = x ++ y
interactiveConcat1 = do {x <- getLine;
                         y <- getLine;
                         putStrLn (g x y);
                         return ()}

这让我觉得我在做 C,除了 emacs 不能自动缩进。我试着用更 Lispy 的风格来写这个:

interactiveConcat2 = getLine >>= (\x ->
                     getLine >>= (\y ->
                     putStrLn (g x y) >>
                     return () ))

这看起来一团糟,最后有一串右括号,你必须数一数(不过,emacs 可以在 Lisp 中可靠地协助完成这项任务,但在 Haskell 中则不行)。另一种方式是说

import Control.Applicative
interactiveConcat3 = return g <*> getLine <*> getLine >>= putStrLn

它看起来很简洁,但不是基础语言的一部分。

从 IO 污点盒中剥离值是否有更省力的符号?也许有更简洁的方法使用 lift* 或 fmap?我希望问什么是 "idiomatic"?

不会太主观

此外,如果有任何使 emacs 比 (Haskell Ind) 模式更好协作的技巧,我们将不胜感激。谢谢!

约翰

编辑:我偶然发现 https://wiki.haskell.org/Do_notation_considered_harmful 并意识到我编写的 lambda 链中的嵌套括号不是必需的。然而,社区(和 ghc 实现者)似乎已经接受了使用 、<*> 等的 Applicative 风格,这似乎使代码更易于阅读,尽管弄清楚运算符优先级令人头疼。

您可以使用 liftA2(或 Control.Monad 中的 liftM2):

import Control.Applicative (liftA2)
liftA2 g getLine getLine >>= putStrLn

注:这个post是写文的Haskell。您可以将其保存为 Main.lhs 并在您的 GHCi 中尝试。


首先简短说明:您可以去掉 do 中的分号和大括号。此外,putStrLn 具有类型 IO (),因此您不需要 return ():

interactiveConcat1 = do 
  x <- getLine
  y <- getLine
  putStrLn $ g x y

我们将使用 IO,因此导入 Control.ApplicativeControl.Monad 会派上用场:

> module Main where
> import Control.Applicative

> -- Repeat your definition for completeness
> g :: [a] -> [a] -> [a]
> g = (++)

您正在寻找这样的东西:

> interactiveConcat :: IO ()
> interactiveConcat = magic g getLine getLine >>= putStrLn

magic需要什么类型?它 returns 一个 IO String,接受一个 returns 一个 String 的函数,通常需要 Strings,并且需要两个 IO Strings:

magic :: (String -> String -> String) -> IO String -> IO String -> IO String

我们大概可以将这种类型概括为

> magic :: (a -> b -> c) -> IO a -> IO b -> IO c

来自 Control.Monadquick hoogle search reveals that there are already two functions with almost that type: liftA2 from Control.Applicative and liftM2。它们是为每个 Applicative 和 – 在 liftM2Monad 的情况下定义的。由于 IO 是两者的实例,您可以选择其中之一:

> magic = liftA2

如果你使用GHC 7.10或更高版本,你也可以使用<$><*>而不导入,并将interactiveConcat写成

interactiveConcat = g <$> getLine <*> getLine >>= putStrLn

为了完整性,让我们添加一个 main 以便我们可以通过 runhaskell Main.lhs:

轻松检查此功能
> main :: IO ()
> main = interactiveConcat

一个简单的检查表明它按预期工作:

$ echo "Hello\nWorld" | runhaskell Main.lhs
HelloWorld

参考资料