在case of中使用守卫语句

1
2
3
4
checkConditions :: Int -> String
checkConditions x = case x of
n | n > 5 && even n -> "Greater than 5 and even"
_ -> "Other cases"

n | n > 5 && even n定义一个n,且n大于5且n为偶数

1
2
3
4
let payStatusCondition = 
case payStatus of
Just ps | Just psInt <- readMaybe ps -> [ChargeLogPayStatus ==. psInt]
_ -> []

为何使用|而不是用&&是因为 && 是一个逻辑运算符,它用于连接两个布尔表达式并返回一个布尔值,而守卫 | 后的条件可以包含模式匹配

ghcup

ghcup 是一个用于管理 Haskell 工具链的命令行工具,它可以帮助你轻松安装、更新、切换 Haskell 编译器、GHC(Glasgow Haskell Compiler)版本以及其他相关工具,如 cabal 和 stack。通过 ghcup,你可以方便地管理和配置 Haskell 开发环境,确保不同项目使用不同的工具链版本。

非管理员运行powershell,若ghcup官网下载缓慢,使用镜像网站下载,如下

1
$env:BOOTSTRAP_HASKELL_YAML = 'https://mirrors.ustc.edu.cn/ghcup/ghcup-metadata/ghcup-0.0.6.yaml'

执行安装脚本

1
Set-ExecutionPolicy Bypass -Scope Process -Force;[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072;Invoke-Command -ScriptBlock ([ScriptBlock]::Create((Invoke-WebRequest https://mirrors.ustc.edu.cn/ghcup/sh/bootstrap-haskell.ps1 -UseBasicParsing))) -ArgumentList $true

安装完成后需要使用ghcup安装和更新软件,如stack等,添加中国镜像
在安装目录下修改配置文件D:\ghcup\config.yml

1
2
url-source:
OwnSource: https://mirrors.ustc.edu.cn/ghcup/ghcup-metadata/ghcup-0.0.6.yaml

stack

stack 是一个 Haskell 的构建工具,它简化了 Haskell 项目的构建、管理和部署过程。stack 提供了一些强大的功能,使得 Haskell 开发变得更加高效和容易。其核心功能包括依赖管理、项目构建、版本控制以及与 GHC(Glasgow Haskell Compiler)集成等。

使用ghcup安装/更新stack版本

1
ghcup install stack

新建项目

1
stack new projectname

新建项目需要下载resolver文件,所以需要代理,clash全局代理对控制台无效,虽然能够curl google.com,但是stack依旧不能走代理,所以添加了代理的环境变量

1
2
$env:HTTP_PROXY="http://127.0.0.1:7890"
$env:HTTPS_PROXY="http://127.0.0.1:7890"

使用建议

如果是开发服务端程序建议在linux环境安装,可在windows的wsl子系统安装,因为访问数据库的一些so文件如果换环境还需要进行匹配。

如果是开发客户端程序那么可以使用windows进行开发,可配合msys2开发,更加方便。

Monad在haskell中是非常常见的操作,一个函数要在不同的Monad中工作就离不开lift。

lift

来自 MonadTrans 类,当使用 Monad Transformer(如 StateT, ReaderT, MaybeT 等)时,操作底层 Monad(例如 IO)需要用 lift。

1
lift :: Monad m => m a -> t m a
1
2
3
4
5
6
7
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.State

example :: StateT Int IO ()
example = do
lift $ putStrLn "This is lifted from IO"
modify (+1)

liftM

是 fmap 的一个特化,用于 Monad,而不是 Functor。将一个二元函数提升为Monad操作

1
liftM :: Monad m => (a -> b) -> m a -> m b
1
2
3
4
import Control.Monad (liftM)

example :: IO Int
example = liftM (+1) (return 41) -- 结果是 IO 42

(+1)操作本来接收一个Int,提升后可以接收一个IO Int

在 GHC 7.10 后,Applicative 成为了 Monad 的超类,因此可以直接用 fmap,推荐用 fmap 或 <$> 而不是 liftM。

1
2
3
4
import Control.Monad (liftM)

example :: IO Int
example = (+1) <$> (return 41) -- 结果是 IO 42

liftA

liftA 针对的是 Applicative,而 liftM 针对的是 Monad。由于 Monad 是 Applicative 的子类,现在两者的功能重叠,通常用 <$>。

liftIO

用于将 IO 操作提升到任何支持 MonadIO 的 Monad 中

1
liftIO :: MonadIO m => IO a -> m a
1
2
3
4
5
6
import Control.Monad.IO.Class (liftIO)

example :: StateT Int IO ()
example = do
liftIO $ putStrLn "This is an IO action"
modify (+1)

liftHandler

yesod框架用于提升handler到另一个Monad中

1
2
3
4
5
6
7
8
9
10
11
12
13
class (MonadResource m, MonadLogger m) => MonadHandler m where
type HandlerSite m
type SubHandlerSite m
liftHandler :: HandlerFor (HandlerSite m) a -> m a
liftSubHandler :: SubHandlerFor (SubHandlerSite m) (HandlerSite m) a -> m a

instance MonadHandler (HandlerFor site) where
type HandlerSite (HandlerFor site) = site
type SubHandlerSite (HandlerFor site) = site
liftHandler = id
{-# INLINE liftHandler #-}
liftSubHandler (SubHandlerFor f) = HandlerFor f
{-# INLINE liftSubHandler #-}

fromMaybe

描述:当 Maybe 值为 Nothing 时,返回一个默认值。

类型签名

1
fromMaybe :: a -> Maybe a -> a

示例

1
2
3
4
5
import Data.Maybe (fromMaybe)

-- 如果值为 Nothing,将使用默认值 10
let result = fromMaybe 10 Nothing -- 结果为 10
let result2 = fromMaybe 10 (Just 5) -- 结果为 5

readMaybe

描述:解析字符串为指定类型(如 Int),如果解析失败则返回 Nothing。

类型签名

1
readMaybe :: Read a => String -> Maybe a

示例

1
2
3
4
import Text.Read (readMaybe)

let parsedInt = readMaybe "123" :: Maybe Int -- 结果为 Just 123
let parsedFail = readMaybe "abc" :: Maybe Int -- 结果为 Nothing

mapMaybe

描述:对列表中的每个元素进行转换,并过滤掉转换失败的元素。

类型签名

1
mapMaybe :: (a -> Maybe b) -> [a] -> [b]

示例

1
2
3
4
import Data.Maybe (mapMaybe)

-- 将字符串列表尝试转换为 Int,仅保留成功转换的结果
let result = mapMaybe readMaybe ["123", "abc", "456"] -- 结果为 [123, 456]

catMaybes

描述:将列表中所有 Just 的值提取出来,忽略 Nothing。

类型签名

1
catMaybes :: [Maybe a] -> [a]

示例

1
2
3
import Data.Maybe (catMaybes)

let result = catMaybes [Just 1, Nothing, Just 2, Nothing, Just 3] -- 结果为 [1, 2, 3]

maybe

描述:将 Maybe 转换为另一个值。第一个参数为 Nothing 的转换值,第二个参数为 Just 的转换函数。

类型签名

1
maybe :: b -> (a -> b) -> Maybe a -> b

示例

1
2
let result = maybe 0 (*2) (Just 5)    -- 结果为 10
let result2 = maybe 0 (*2) Nothing -- 结果为 0

hoistMaybe

描述:将一个普通值转换为 Maybe 值:如果条件满足则返回 Just,否则返回 Nothing。

类型签名

1
hoistMaybe :: Bool -> a -> Maybe a

示例

1
2
3
4
5
hoistMaybe :: Bool -> a -> Maybe a
hoistMaybe condition value = if condition then Just value else Nothing

let result = hoistMaybe (5 > 3) "符合条件" -- 结果为 Just "符合条件"
let result2 = hoistMaybe (5 < 3) "不符合条件" -- 结果为 Nothing

指引

先来看一个例子,在后端收到一个请求后需要解析该请求的请求头获取token,获取到token后需要从redis中查询该token的用户数据。现在有个现成的方法如下

1
2
3
4
5
6
-- 获取请求头的token
lookupBearerAuth :: Handler (Maybe String)
-- 根据key从redis查询value
getFromRedis :: String -> Handler (Maybe String)
-- 将user的json字符串转为数据
decodeUser :: String -> Handler (Maybe User)

那么现在获取用户信息json的方法如下

1
2
3
4
5
6
7
8
9
10
getUser :: Handler (Maybe User)
getUser = do
mToken <- lookupBearerAuth
case mToken of
Nothing -> return Nothing
Just token -> do
mUserJson <- getFromRedis token
case mUserJson of
Nothing -> return Nothing
Just userJson -> decodeUser userJson

可以发现,因为每一步都存在Nothing的可能性,所以每一步都需要判断,那么有没有什么简写的方式呢?答案是,有的。

实现思路:Monad的隐式操作

Monad的签名如下

1
2
3
4
class Applicative m => Monad m where
(>>=) :: m a -> (a -> m b) -> m b -- bind 操作符
(>>) :: m a -> m b -> m b -- 序列操作
return :: a -> m a -- 注入操作

>>=用于将m a转为m b,其中(a -> mb)是转换的方法。形象的理解就是m a可以当成包含元素a的集合,m b可以当成包含元素b的集合,a -> m b就是将元素a转为包含元素b的集合,用js的命令式写法就是

1
2
3
function >>=(list: a[], fun: (item: a) => b[]): b[]{
return list.flatmap(fun)
}

当然,m不等于数组,这里只是为了形象。
在实现的内容里,我们除了可以使用a -> m b来处理m a中的元素,我们还能添加额外的操作,而这些额外的操作在doreturn语法糖中是看不到的。

下面是一个实现隐式操作的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
data MyContainer a = MyContainer a

-- 为MyContainer实现Monad方法,定义>>=的逻辑
instance Monad MyContainer where
(>>=) :: MyContainer a -> (a -> MyContainer b) -> MyContainer b -- bind 操作符
MyContainer a >>= f =
-- 额外的操作
doSomeThing
-- 将转换函数应用于a
f a

-- 转换函数
appendB :: String -> MyContainer String
appendB str = MyContainer $ str ++ "B"

-- 使用MyContainer
test :: MyContainer String
test = MyContainer "A" >>= appendB

-- test2是test1的do写法
test2 :: MyContainer String
test2 = do
a <- MyContainer "A"
-- 执行了doSomething隐式操作
appendB a

知道了Monad可以实现隐式操作之后,我们就可以将Nothing的判断放到Monad的隐式操作中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
data MaybeT a = MaybeT
{ maybeTValue :: Maybe a
}

instance Monad m => Monad MaybeT where
return = MaybeT . Just

-- 根据Monad的签名,可以推导出x和f的定义
-- x: MaybeT a
-- f: a -> MaybeT b
x >>= f =
let v = maybeTValue x
in case v of
-- 若value是nothing不处理
Nothing -> return Nothing
Just y -> (f y)

主题:MaybeT

下面是mtl包下的MaybeT的Monad实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
newtype MaybeT m a = MaybeT { 
runMaybeT :: m (Maybe a)
}

-- (MaybeT m)的monad实现
instance Monad m => Monad (MaybeT m) where
return = MaybeT . return . Just

-- 根据Monad的签名,可以推导出x和f的定义
-- x: MaybeT m a
-- f: a -> MaybeT m b
(>>=) x f = MaybeT $ do
v <- runMaybeT x
case v of
Nothing -> return Nothing
Just y -> runMaybeT (f y)

对比上面的设计,这种设计增加了一个Monad参数m。这么做的原因是因为MaybeT 的主要目的是作为 Monad Transformer,使 Maybe Monad 能够与其他 Monad(如 IO、List 等)堆叠。通过定义为 MaybeT m a = m (Maybe a),它可以包裹任何 Monad 上下文 m,使其兼容多种 Monad,而不是仅限于单一的 Maybe 值。

使用MaybeT实现隐式判断nothing

1
2
3
4
5
6
7
getUser :: Handler (Maybe User)
getUser = runMaybeT $ do
token <- MaybeT lookupBearerAuth
-- 隐式执行判断
userJson <- MaybeT $ getFromRedis token
-- 隐式执行判断
decodeUser userJson

总结,Maybe的Monad实现使得在Maybe的上下文中能够自动过滤Nothing,但在m (Maybe a)是在m的上下文,为了使其能实现Maybe上下文相同的效果,通过MaybeT包装m (Maybe a),并为其实现过滤Nothing的逻辑。

MonadPlus 是 Haskell 中的一个类型类,它结合了 Monad 和 Alternative 的特性,提供了额外的处理组合逻辑和失败的能力。

它在处理带有失败、非确定性和多重选择的计算时非常有用,常见的实例包括 Maybe、[] (列表) 和 Either。

MonadPlus 定义了两个主要操作:

  • mzero:表示一个失败或空的计算。
  • mplus:表示两个计算的组合,提供了一个“或”操作。

常见应用场景

错误处理和短路

Maybe 是 MonadPlus 的一个实例,可以在链式操作中表示失败。例如,多个计算中只要有一个失败(即 Nothing),就直接返回失败。

1
2
3
4
5
6
7
8
9
import Control.Monad (mplus)

findValue :: Int -> Maybe String
findValue x
| x == 1 = Just "One"
| x == 2 = Just "Two"
| otherwise = Nothing

test = findValue 1 `mplus` findValue 3 -- 结果为 Just "One"

如果第一个 findValue 1 成功,那么结果为 Just “One”;如果失败(返回 Nothing),则继续尝试第二个计算。

非确定性选择

列表([])是 MonadPlus 的另一个实例,可以用于处理多个选项的情况。比如,生成所有满足特定条件的组合。

1
2
3
4
5
6
7
8
9
10
import Control.Monad (guard)

filteredPairs :: [(Int, Int)]
filteredPairs = do
x <- [1, 2]
y <- [3, 4]
guard (x + y > 4) -- 仅保留和大于 4 的组合
return (x, y)

-- 结果为 [(1, 4), (2, 3), (2, 4)]

列表类型表示一种“非确定性”计算——一个列表可以包含多个可能的值。
这个例子中,通过mplus生成所有可能的分支,guard函数再对部分不符合条件的分支返回mzero。

mplus并不是直接使用,而是通过>>=使用了concatMap达到mplus的效果。

1
2
3
4
5
6
7
8
filteredPairs :: [(Int, Int)]
filteredPairs = do
x <- [1, 2]
y <- [3, 4]
return (x, y)

-- 没有concat的结果是[[(1, 3), (1, 4)], [(2, 3), (2, 4)]]的两个数组
-- 有了concat结果为 [(1, 3), (1, 4), (2, 3), (2, 4)],实现了类似++的结果

至于为什么能变成四个组合,可以不用语法糖do来理解

1
2
3
4
5
filteredPairs :: [(Int, Int)]
filteredPairs = [1, 2] >>= \x -> [3, 4] >>= \y -> return (x, y)

filteredPairs :: [(Int, Int)]
filteredPairs = [(x, y) | x <- [1, 2], y <- [3, 4]]
1
2
3
4
5
6
7
instance MonadPlus [] where
mzero = [] -- 表示一个空列表,类似于“没有结果”或“失败”
mplus = (++) -- 列表的组合操作,即将两个列表拼接在一起

instance Monad [] where
return x = [x]
xs >>= f = concatMap f xs -- 使用 concatMap 包含了++的功能

使用 guard 进行条件过滤

guard的实现依赖于MonadPlus

1
2
3
guard        :: (MonadPlus m) => Bool -> m ()
guard True = return ()
guard False = mzero

可以使用 guard(来自 Control.Monad)在 MonadPlus 上添加条件限制。例如,过滤出满足条件的值。

1
2
3
4
5
6
7
8
9
import Control.Monad (guard)

filterEven :: [Int] -> [Int]
filterEven xs = do
x <- xs
guard (even x) -- 保留偶数
return x

-- 结果为 [2,4,6],假设输入是 [1,2,3,4,5,6]

组合计算链中的失败和成功

在需要依次尝试多种方法来解决问题时,mplus 可以帮助实现这种链式的短路操作。

1
2
3
4
5
6
safeDivide :: Int -> Int -> Maybe Int
safeDivide _ 0 = Nothing
safeDivide x y = Just (x `div` y)

result = safeDivide 10 0 `mplus` safeDivide 10 2
-- 结果为 Just 5,因为第一个计算失败,尝试第二个

总结

MonadPlus 的应用主要体现在处理非确定性、多重选择、错误短路等场景中,适用于需要组合多种可能性的计算。

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE FlexibleContexts #-}

module Lib
( someFunc
) where

import Control.Monad (liftM, ap)

-- Step 1: 定义 ReaderT 类型
newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }

-- Step 2: 实现 Functor 实例
instance Functor m => Functor (ReaderT r m) where
fmap f (ReaderT r) = ReaderT $ \env -> fmap f (r env)

-- Step 3: 实现 Applicative 实例
instance Monad m => Applicative (ReaderT r m) where
pure x = ReaderT $ \_ -> pure x
(ReaderT f) <*> (ReaderT x) = ReaderT $ \env -> do
func <- f env
val <- x env
return (func val)

-- Step 4: 实现 Monad 实例
instance Monad m => Monad (ReaderT r m) where
return x = ReaderT $ \_ -> return x
(ReaderT r) >>= k = ReaderT $ \env -> do
a <- r env
let ReaderT r' = k a
r' env

-- Step 5: 实现 MonadReader 类
class Monad m => MonadReader r m | m -> r where
ask :: m r
local :: (r -> r) -> m a -> m a

-- Step 6: 实现 ReaderT 的 MonadReader 实例
instance Monad m => MonadReader r (ReaderT r m) where
ask = ReaderT return
local f (ReaderT r) = ReaderT $ \env -> r (f env)

-- Step 7: 定义 someFunc 测试函数
testReaderT :: IO ()
testReaderT = do
let reader = ReaderT $ \env -> return ("Environment: " ++ env)
result <- runReaderT reader "TestEnv"
putStrLn result

let askReader = ask :: ReaderT String IO String
env <- runReaderT askReader "TestEnv"
putStrLn ("ask: " ++ env)

let localReader = local (\env -> env ++ " Modified") askReader
modifiedEnv <- runReaderT localReader "TestEnv"
putStrLn ("local: " ++ modifiedEnv)

-- 修改后的 testMonadReader 函数,启用 FlexibleContexts
testMonadReader :: (MonadReader String m) => m String
testMonadReader = do
env <- ask -- 获取环境变量,这里 r 被具体化为 String
let modifiedEnv = env ++ " Modified"
return modifiedEnv

someFunc :: IO ()
someFunc = do
-- 运行 ReaderT 测试
testReaderT

-- 运行使用 MonadReader 约束的测试
let result = testMonadReader :: ReaderT String IO String
modifiedEnv <- runReaderT result "TestEnv"
putStrLn ("testMonadReader: " ++ modifiedEnv)

对于一个需要环境变量r的函数,既可以使用MonadReader约束从而运行在不同的上下文中,也可以使用ReaderT作为签名。

本质

ReaderT的本质就是一个r -> m a的函数,m a也是一个函数,通过闭包的方式访问r。

lift

1
2
3
4
5
class MonadTrans t where
lift :: Monad m => m a -> t m a

instance MonadTrans (ReaderT r) where
lift ma = ReaderT $ \_ -> ma

1
2
3
4
5
6
7
8
9
10
class DictEnum a where
dictLabel :: a -> Text
dictValues :: [a]
default dictValues :: (Enum a) => [a]
dictValues = enumFrom (toEnum 0)

instance (DictEnum a) => ToJSON a where
toJSON = object [ "label" .= dictLabel val
, "value" .= fromEnum val
]

我想对任意实现了DictEnum的类型自动实现ToJSON,因为他们的json结构是一样的。

如果按照上述方式写,会有问题。因为上述的ToJSON实现针对任意类型,那么没有实现ToJSON的类型会自动使用这个实现,这就导致了不需要实现DictEnum的类型会报错说没有实现DictEnum。

所以,修改了代码改成如下方式

1
2
3
4
5
6
7
8
9
10
class DictEnum a where
dictLabel :: a -> Text
dictValues :: [a]
default dictValues :: (Enum a) => [a]
dictValues = enumFrom (toEnum 0)

defaultDictToJson :: (DictEnum a, Enum a) => a -> J.Value
defaultDictToJson val = object [ "label" .= dictLabel val
, "value" .= fromEnum val
]

在使用的地方实现ToJSON

1
2
instance ToJSON ProficiencyEnum where
toJSON = defaultDictToJson

1
newtype ServeFor site param a = ServeFor (ReaderT (ServiceContext site param) (HandlerFor site) a) 

该类型是一个多参数类型,现在需要对其写个liftS,使得ServeFor可以提升到其他Monad

1
2
3
4
5
class Monad m => MonadServe site param m where
liftS :: ServeFor site param a -> m a

instance MonadServe site param (DbServeFor site param) where
liftS = DbServeFor . lift

这时候两个site和两个param并不相等,所以在DbServeFor中调用liftS并无法将ServeFor提升到DbServeFor。

所以需要添加约束使得前后的site和param相等,这时候有多种方式。

方式1:添加一个函数使用forall约束

1
2
liftSDBS :: forall site param a. ServeFor site param a -> DbServeFor site param a
liftSDBS = liftS

这种方式比较冗余,为了添加约束需要另外定义一个函数

方式2:调用的时候提供类型推导

1
2
3
4
5
6
7
8
test :: DbServeFor site param a
test po domain = do
ServiceContext userId now _ <- (liftS ctx :: DbServeFor site param)

-- 添加TypeApplications
test2 :: DbServeFor site param a
test2 po domain = do
ServiceContext userId now _ <- liftS @site @param ctx

方式3:Scoped Type Variables(推荐)

在定义class的时候就添加约束

1
2
3
4
5
6
7
8
9
class Monad m => MonadServe m where
type ServeSite m
type ServeParam m
liftS :: ServeFor (ServeSite m) (ServeParam m) a -> m a

instance MonadServe (DbServeFor site param) where
type ServeSite (DbServeFor site param) = site
type ServeParam (DbServeFor site param) = param
liftS = DbServeFor . lift

type ServeSite mtype ServeParam m不仅仅是为了转移了class的参数,还说明了这两个属性从m推导。

1
2
3
4
5
6
7
8
9
-- site param m各自独立,没有关系
class Monad m => MonadServe site param m where
liftS :: ServeFor site param a -> m a

-- ServeSite m和ServeParam m可以获取m的属性
class Monad m => MonadServe m where
type ServeSite m
type ServeParam m
liftS :: ServeFor (ServeSite m) (ServeParam m) a -> m a

函数

1
2
3
4
5
6
7
8
9
10
handleDBException :: SomeException -> HandlerFor site (Either DatabaseException a)
handleDBException e =
let eMsg = T.pack $ show e
in
if "Duplicate" `T.isInfixOf` eMsg
then return $ Left DuplicateEntryException
else return $ Left $ GeneralDatabaseException eMsg

tryDbAction :: YesodDB site a -> HandlerFor site (Either DatabaseException a)
tryDbAction dbAction = ( Right <$> dbAction) `catch` handleDBException

上述函数并没有使用到HandlerFor这个类型相关的函数,完全可以用Monad约束代替。

1
2
3
4
5
6
7
8
9
10
handleDBException :: Monad m => SomeException -> m (Either DatabaseException a)
handleDBException e =
let eMsg = T.pack $ show e
in
if "Duplicate" `T.isInfixOf` eMsg
then return $ Left DuplicateEntryException
else return $ Left $ GeneralDatabaseException eMsg

tryDbAction :: (Functor m, Monad m, MonadUnliftIO m) => m a -> m (Either DatabaseException a)
tryDbAction dbAction = ( Right <$> dbAction) `catch` handleDBException

newtype

在构建newtype的时候也是一样,例如构建一个ReaderT

1
2
newtype ServeFor site param a = ServeFor (ReaderT (ServiceContext param) (HandlerFor site) a)
deriving (Functor, Applicative, Monad, MonadIO, MonadResource, MonadLogger)

也可以修改如下

1
newtype Monad m => ServeFor param m a = ServeFor (ReaderT (ServiceContext param) m a)
0%