0

我正在尝试使用 Haskell 和 Framework Scotty 构建一个简单的博客。使用 Model.hs 我有:

data Post = Post
    { id :: Int
    , tipo :: String
    , titulo :: String
    , conteudo :: String
    } deriving (Show, Generic)

我已经使用 sqlite 创建了一个模式并填充了一些数据,现在我正在尝试在我的 Storage.hs 中使用此方法获取这些数据

selectPosts :: Sql.Connection -> IO [M.Post]
selectPosts conn =
    Sql.query_ conn "select * from post" :: IO [M.Post]

我的意图是在我的 Main.hs 中获取 json 格式的数据:

instance ToJSON M.Post
instance FromJSON M.Post

main :: IO ()
main = do
    putStrLn "Starting Server..."
    scotty 3000 $ do
        get "/" $ file "templates/index.html"
        get "/posts" $ do
            json posts where
            posts = withTestConnection $ \conn -> do
                S.selectPosts conn

但是我得到一个 IO [Model Post],我不知道如何将它呈现为 json,所以它不断收到这个错误:

No instance for (ToJSON (IO [Post])) arising from a use of ‘json’

我的项目在github上运行,只需使用 stack build 和 stack ghci 之后。在建筑物中,我已经收到此错误。

4

1 回答 1

1

在 Haskell 中,所有函数都是纯函数 --- 所以像selectPosts需要出去做 IO 来与数据库对话的东西,不能只是这样做并从数据库中返回值。取而代之的是,这些类型的函数返回 type 的东西IO a,您可以将其视为基本描述如何走出去执行 IO 以获取 type 的值a。这些“IO动作”可以组合在一起,其中一个可以分配给main;在运行时,RTS 将执行这些 IO 操作。

但是,您并没有将IO a返回的值组合成最终变为selectPosts的更大值的一部分;您正试图通过将其输入. 这是行不通的,因为没有(好的/简单的)方法可以将如何执行 IO 的描述转换为 JSON 字符串。IOmainjson

Haskell 处理组合这些值的方式是通过称为“monad”的抽象,这在许多其他情况下也很有用。do符号可用于以非常自然的风格编写这种单子序列。您不能只写posts <- withTestConnection S.selectPosts在这里,因为 Scotty 的get函数采用单子ActionM类型的值,而不是IO. 然而,事实证明这ActionM基本上是一堆其他有用的东西分层在顶部IO,所以应该可以将 IO 动作从selectPostsScotty 的ActionMmonad 中“提升”:

get "/posts" $ do
  posts <- liftIO $ withTestConnection S.selectPosts
  json posts

旁注:您可能已经注意到我写withTestConnection S.selectPosts的不是withTestConnection $ \conn -> do S.selectPosts conn. 一般来说,如果你有一个do只有一个表达式的块(不是形式x <- act),这与块外的单个表达式相同do\conn -> S.selectPosts conn。此外,Haskell 倾向于鼓励部分应用:你有S.selectPosts,这是一个函数Sql.Connection -> IO [M.Post]\conn -> S.selectPosts conn是另一个相同类型的函数,它将连接传入selectPosts然后返回相同的结果selectPosts——这个函数与它自己没有区别selectPosts!因此,如果这就是您在 . 中所需要的全部内容withTestConnection,您应该能够将整个 lambda 和do块简化为S.selectPosts.

于 2018-07-05T19:38:19.283 回答