在Haskell do语句中合并动作

Raf*_*ida 4 monads haskell functional-programming

我正在尝试学习haskell中的语句如何工作。我试图制作一个非常简单的程序,您可以在其中调用REST端点并执行系统命令(诸如“ ls”之类的非常简单的问题),问题在于将不同的动作类型组合在一个do语句中。


import Web.Scotty
import System.Cmd

main = do
  putStrLn "Starting Server"
  scotty 3000 $ do
    get "/url" $ do                         
      system "ls"
      text "Success"
Run Code Online (Sandbox Code Playgroud)

但是我得到了下一个编译器错误:

Main.hs:12:7:
    Couldn't match expected type ‘Web.Scotty.Internal.Types.ActionT
                                    Data.Text.Internal.Lazy.Text IO a0’
                with actual type ‘IO GHC.IO.Exception.ExitCode’
    In a stmt of a 'do' block: system "ls"
    In the second argument of ‘($)’, namely
      ‘do { system "ls";
            text "Success" }’

Run Code Online (Sandbox Code Playgroud)

我很难学习Haskell!

dan*_*iaz 6

In Haskell, do-notation is used to chain statement-like things. A statement is some type constructor like IO applied to a particular result type. For example, the statement system "ls" has type IO ExitCode.

Other type constructors other than IO can work as statements. All that do-notation requires is that the type constructor implements the Monad interface which explains how to chain statements sensibly.

However, within a single do-block, only one type of statement is allowed! They must be all IO statements, or all ActionT Text IO statements. In your example you are mixing the two, which causes the error. Scotty's get function expects an ActionT Text IO statement:

get :: RoutePattern -> ActionM () -> ScottyM ()
-- ActionM is actually a synonym for ActionT Text IO
Run Code Online (Sandbox Code Playgroud)

The good news is that there's a way to convert (the usual Haskell term is "lift") IO statements into ActionT Text IO statements. The latter are actually a kind of "decorator" (the usual Haskell term is "monad transformer") over IO actions, which enable extra functionality related to Scotty. You can "lift" IO actions into the decorator using the liftIO function, like this:

get "/url" $ do                         
     liftIO (system "ls")
     text "Success"
Run Code Online (Sandbox Code Playgroud)

In general, when can we use liftIO to lift a plain IO statement into a "decorated" statement? The "decorator" type constructor must have a MonadIO instance besides the usual Monad instance. MonadIO is what provides the liftIO function.

In our case, looking at the available instances for ActionT:

(MonadIO m, ScottyError e) => MonadIO (ActionT e m)
Run Code Online (Sandbox Code Playgroud)

Which means something like "if m is has a MonadIO instance—like IO trivially does—and the error type e has a ScottyError instance—like Text does—then we can lift IO statements to ActionT e m statements".

And the specialized type for liftIO is:

liftIO :: IO a -> ActionT Text IO a
Run Code Online (Sandbox Code Playgroud)

  • @Bergi奇怪的是,Haskell报告在参考[语法](https://www.haskell.org/onlinereport/haskell2010/haskellch3.html#x8-470003.14)中使用了“ do {stmts}”,而将`;`称为一个“空声明”,所以我猜官方用语是声明。由于您提到的原因,这可能不是最好的术语。 (3认同)