Объединение действий в заявлении haskell do - PullRequest
4 голосов
/ 02 ноября 2019

Я пытаюсь узнать, как работают операторы в 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"

Но я получаю следующую ошибку компилятора:

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" }’

Я с трудом пытаюсь выучить Haskell!

1 Ответ

6 голосов
/ 02 ноября 2019

В Haskell do -обозначение используется для цепочки вещей, похожих на операторы. Оператор - это конструктор типа, подобный IO, применяемый к определенному типу результата. Например, оператор system "ls" имеет тип IO ExitCode.

Другие конструкторы типов, кроме IO, могут работать как операторы. Все, что требуется для do -notation, - это то, что конструктор типов реализует интерфейс Monad , который объясняет, как разумно объединять операторы в цепочку. do -блок, допускается только один тип заявления ! Это должны быть все операторы IO или все операторы ActionT Text IO. В вашем примере вы смешиваете два, что вызывает ошибку. Функция Скотти get ожидает оператор ActionT Text IO:

get :: RoutePattern -> ActionM () -> ScottyM ()
-- ActionM is actually a synonym for ActionT Text IO

Хорошая новость заключается в том, что существует способ преобразования (обычный термин на Haskell - это "lift ") IO операторы в ActionT Text IO операторы. Последние на самом деле являются своего рода «декоратором» (обычный термин на Haskell - «монадный преобразователь» ) над IO действиями, которые обеспечивают дополнительную функциональность, связанную со Скотти. Вы можете «поднять» IO действия в декоратор, используя функцию liftIO, например:

get "/url" $ do                         
     liftIO (system "ls")
     text "Success"

В общем, когда мы можем использовать liftIO, чтобы поднятьобычное IO утверждение в «украшенное» утверждение? Конструктор типа «decorator» должен иметь экземпляр MonadIO помимо обычного Monad экземпляра. MonadIO - это то, что обеспечивает функцию liftIO.

В нашем случае, рассмотрим доступные экземпляры для ActionT:

(MonadIO m, ScottyError e) => MonadIO (ActionT e m)

Что означает что-тонапример, "если m имеет экземпляр MonadIO, как это обычно делает IO, а тип ошибки e имеет экземпляр ScottyError, как это делает Text, то мы можем поднять операторы IO доActionT e m операторов ".

А специализированный тип для liftIO:

liftIO :: IO a -> ActionT Text IO a
...