关于Haskell:如何在scotty和selda中使用monad堆栈?

How to use a monad stack with scotty and selda?

我一直在尝试使运行scotty的Web服务器可以使用selda与我的数据库进行通信。我认为使用monad变压器堆栈将是完成类似任务的方式。我一直在尝试解决问题,但遇到了一些死胡同,这些类型似乎根本无法工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{-# LANGUAGE DeriveGeneric, OverloadedStrings, OverloadedLabels #-}

module Server where

import Web.Scotty
import Data.Monoid (mconcat)
import Data.Aeson (ToJSON)
import GHC.Generics
import Web.Scotty
import Database.Selda
import Database.Selda.SQLite
import Control.Monad.Trans.Class

import Models

type App = SeldaT SQLite ScottyM

-- withPersist:: (MonadIO m, MonadMask m) => SeldaT SQLite m a -> m a
server = scotty 4200 (withPersist router)

router :: App ()
router = do
  lift $ get"/book/:id" searchBook

searchBook:: ActionM ()
searchBook = do
  books <- query selectBookQuery
  json books
    where
      selectBookQuery = do
        book <- select goodreadsBooks
        restrict (book ! #goodreadsId .=="20")
        return book

我试图根据此处的答案来宽松地建立它,但是我想package路由器而不是单独的路线。我不希望我打开的连接数与我拥有的路由数成正比,如果可以避免的话,我也不希望每个路由都必须有一个withPersist调用。铅>

所以我有一个App类型的SeldaT Sqlite ScottyM,然后使用withPersist(它是== withSQLite"mydb.db"),我会将那个SeldaT Sqlite ScottyM变成一个ScottyM 。不过还是有很多问题,这是我对它们的理解:

  • SeldaT m a(MonadIO m, MonadMask m)约束,并且ScottyM没有MonadIO的实例
  • Scotty.get返回一个ScottyM (),我觉得这是我要使用lift将其变成SeldaT Sqlite ScottyM的地方,但是我遇到了一个错误,可能与ScottyM不是一个实例有关从上方MonadIO的。
  • 由于searchBook仍然是ActionM,因此我无法在其中运行查询。不确定如何获取get来接受我的转换器堆栈而不是ActionM

以下是错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/home/marcus/Documents/projects/nowwhatdoiread/nwdir-server/app/Server.hs:19:23: error:
    a€¢ No instance for (MonadIO ScottyM)
        arising from a use of a€?withPersista€?
    a€¢ In the second argument of a€?scottya€?, namely a€?(withPersist router)a€?
      In the expression: scotty 4200 (withPersist router)
      In an equation for a€?servera€?:
          server = scotty 4200 (withPersist router)
   |
19 | server = scotty 4200 (withPersist router)
   |                       ^^^^^^^^^^^^^^^^^^

/home/marcus/Documents/projects/nowwhatdoiread/nwdir-server/app/Server.hs:23:3: error:
    a€¢ No instance for (MonadTrans (SeldaT SQLite))
        arising from a use of a€?lifta€?
    a€¢ In a stmt of a 'do' block: lift $ get"/book/:id" searchBook
      In the expression: do lift $ get"/book/:id" searchBook
      In an equation for a€?routera€?:
          router = do lift $ get"/book/:id" searchBook
   |
23 |   lift $ get"/book/:id" searchBook
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

/home/marcus/Documents/projects/nowwhatdoiread/nwdir-server/app/Server.hs:27:12: error:
    a€¢ No instance for (MonadSelda
                         (Web.Scotty.Internal.Types.ActionT
                            Data.Text.Internal.Lazy.Text IO))
        arising from a use of a€?querya€?
    a€¢ In a stmt of a 'do' block: books <- query selectBookQuery
      In the expression:
        do books <- query selectBookQuery
           json books
      In an equation for a€?searchBooka€?:
          searchBook
            = do books <- query selectBookQuery
                 json books
            where
                selectBookQuery
                  = do book <- select goodreadsBooks
                       ....
   |
27 |   books <- query selectBookQuery
   |            ^^^^^^^^^^^^^^^^^^^^^

更新:在看了更多类似的问题之后,我可能需要使用ScottyT。但是不确定如何将SeldaT嵌套在ScottyT转换器中。


环顾了很多其他答案并了解了有关monad变压器的更多信息后,我才知道了这一点,我想到的解决方案是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{-# LANGUAGE DeriveGeneric, OverloadedStrings, OverloadedLabels #-}

module Server where

import Web.Scotty.Trans
import Database.Selda
import Database.Selda.SQLite
import Control.Monad.Trans.Class
import Control.Monad.Identity
import qualified Data.Text.Lazy as TL

import Models

server :: IO ()
server = scottyT 4200 withPersist router

router :: ScottyT TL.Text (SeldaT SQLite IO) ()
router = do
  get"/book/:id" searchBook

searchBook:: ActionT TL.Text (SeldaT SQLite IO) ()
searchBook = do
  books <- lift $ query selectBookQuery
  json books
    where
      selectBookQuery = do
        book <- select goodreadsBooks
        restrict (book ! #goodreadsId .=="20")
        return book

有几个键:

  • Scotty有自己的monad变压器ScottyT,它必须是最外面的变压器
  • 需要使用Scott.Trans来使变压器堆栈与scotty一起使用,因为类型更为通用
  • 需要使用ActionT转换器进行操作,因此他们也可以访问transformer堆栈