如何以无点样式编写绑定函数?

Wil*_*ney 1 haskell

我经常发现自己必须明确指定return将表达式包装在其中IO/Maybe/Either......但我确信必须有一种更好的方法来使其无点.

例如:

countLines :: FilePath -> IO Int countLines file = readFile file >>= countLines where countLines = (\d -> return $ (length . lines) d)

有没有办法实现功能的无点样式countLines?为每一步写lambda >>=是令人厌倦的.

chi*_*chi 5

countLines :: FilePath -> IO Int
countLines file = readFile file >>= countLines
     where countLines = (\d -> return $ (length . lines) d)
Run Code Online (Sandbox Code Playgroud)

顺便说一句,在where子句中使用函数的相同名称并不是一个好习惯:即使函数不是这样,函数看起来也是递归的.

回到问题,上面的代码意味着

countLines file = readFile file >>= (\d -> return $ (length . lines) d)
Run Code Online (Sandbox Code Playgroud)

意思是

countLines file = readFile file >>= (return . (length . lines))
Run Code Online (Sandbox Code Playgroud)

可以写成

countLines file = length . lines <$> readFile file
-- or
countLines file = fmap (length . lines) $ readFile file
Run Code Online (Sandbox Code Playgroud)

人们可以更进一步,也可以删除这file一点,但可读性会受到轻微伤害.

countLines = fmap (length . lines) . readFile
Run Code Online (Sandbox Code Playgroud)

有没有办法为countLines函数实现无点样式?为>>的每一步写lambda是令人厌倦的.

那么,在一般情况下,无点样式几乎不可读.为什么不使用do notation呢?

countLines :: FilePath -> IO Int
countLines file = do
   d <- readFile file
   return . length . lines $ d
Run Code Online (Sandbox Code Playgroud)

在这种情况下,这并不像pointfree-ish变体那样优雅,但更为通用.即使链接很多>>=s有时会使用以下样式:

foo =
  m1 >>= \x1 ->
  m2 >>= \x2 ->
  m3 >>= \x3 ->
  ...
  mN >>= \xN ->
  return (...)
Run Code Online (Sandbox Code Playgroud)

  • `do`符号实际上只是`>> =`的语法糖.在我看来,当Haskell使代码更容易阅读时,没有理由避免命令式样式.顺序计算最自然地以顺序方式陈述.以参考透明的方式进行顺序计算是monad的存在理由. (3认同)
  • @Wildhoney 一开始避免使用 do 符号可能是有益的,这样可以牢牢掌握下面的内容。但我相信,当人们熟悉 Haskell 时,就不会再将 do 与命令式风格联系起来,除非手头的 monad 是 IO 相关的或有状态的。即使应用风格现在变得更加常见,do 表示法仍然很惯用。 (2认同)

sha*_*am1 5

在这里使你的功能

countLines :: FilePath -> IO Int
countLines file = readFile file >>= countLines
 where countLines = (\d -> return $ (length . lines) d)
Run Code Online (Sandbox Code Playgroud)

pointfree,我们需要先将其重构为:

countLines :: FilePath -> IO Int
countLines file = readFile file >>= countLines
 where countLines = (\d -> return $ (length . lines) $ d)
Run Code Online (Sandbox Code Playgroud)

现在,我们可以用其他函数组合返回,就像这样(我们可以删除括号):

countLines :: FilePath -> IO Int
countLines file = readFile file >>= countLines
 where countLines = (\d -> return . length . lines $ d)
Run Code Online (Sandbox Code Playgroud)

现在我们可以拿出这一点(再次,删除括号):

countLines :: FilePath -> IO Int
countLines file = readFile file >>= countLines
 where countLines = return . length . lines
Run Code Online (Sandbox Code Playgroud)

现在可以内联函数:

countLines :: FilePath -> IO Int
countLines file = readFile file >>= return . length . lines
Run Code Online (Sandbox Code Playgroud)

作为一个额外的好处,如果你想让countLines完全无点,你可以使用Kleisi组合运算符(找到Control.Monad)代替bind,如下所示:

countLines :: FilePath -> IO Int
countLines = readFile >=> return . length . lines
Run Code Online (Sandbox Code Playgroud)

虽然可以说其他版本更具可读性.

最后一个也可以写成:

countLines :: FilePath -> IO Int
countLines = return . length . lines <=< readLine
Run Code Online (Sandbox Code Playgroud)

它的优势在于您可以从右到左阅读该功能,并确切了解该功能如何"流动".