servant 函数依赖性错误与 `enter`
servant a functional dependency error with `enter`
我稍微更改了 servant 教程中显示的应用程序,使 Reader
monad 成为 ReaderT
,就像这样
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeOperators #-}
module Lib
( runServer
) where
import Control.Monad.Except
import Control.Monad.Reader
import qualified Data.Text as T
import Network.Wai
import Servant
type WebApi
= "static" :> Raw
:<|> "foo" :> Get '[PlainText] T.Text
type Foo = String
server :: ServerT WebApi (ReaderT Foo (ExceptT ServantErr IO))
server = static :<|> foo
where
static :: Application
static = undefined
-- Handler T.Text
foo :: ReaderT Foo (ExceptT ServantErr IO) T.Text
foo = undefined
webAPI :: Proxy WebApi
webAPI = Proxy
readerToHandler :: Foo -> ReaderT Foo (ExceptT ServantErr IO) :~> ExceptT ServantErr IO
readerToHandler t = Nat (\x -> runReaderT x t)
-- readerServer :: ServerT WebApi (ExceptT ServantErr IO)
-- readerServer = enter (readerToHandler "foobarbaz") server
-- serve' :: Application
-- serve' = serve webAPI server
runServer :: IO ()
runServer = return ()
问题是我无法启用 readerServer
功能,类型检查失败并出现这个难以理解的错误
src/Lib.hs:45:16: error:
• Couldn't match type ‘IO’ with ‘ExceptT ServantErr IO’
arising from a functional dependency between:
constraint ‘Servant.Utils.Enter.Enter
(IO ResponseReceived)
(ReaderT Foo (ExceptT ServantErr IO) :~> ExceptT ServantErr IO)
(IO ResponseReceived)’
arising from a use of ‘enter’
instance ‘Servant.Utils.Enter.Enter (m a) (m :~> n) (n a)’
at <no location info>
• In the expression: enter (readerToHandler "foobarbaz") server
In an equation for ‘readerServer’:
readerServer = enter (readerToHandler "foobarbaz") server
Failed, modules loaded: none.
知道出了什么问题吗?
问题是 Raw
端点的存在,它与 enter
交互不佳。这是Servant中的known annoyance
Enter
类型类决定了可以转换哪些处理程序集,以及使用哪些转换。它有三个实例:
Enter (m a) ((:~>) m n) (n a)
最简单的情况。如果你有一个 monadic 动作和一个将它带到不同 monad 的自然转换,你可以应用转换。
Enter b arg ret => Enter (a -> b) arg (a -> ret)
。如果您有一个带参数的处理程序并且您知道如何转换处理程序的最终单子操作,则可以使用相同的 arg
转换来转换处理程序。
(Enter typ1 arg1 ret1, Enter typ2 arg2 ret2, (~) * arg1 arg2) => Enter ((:<|>) typ1 typ2) arg1 ((:<|>) ret1 ret2)
如果您有一个处理程序组合 :<|>
,并且每个处理程序都可以使用 相同 自然转换单独转换 arg1
,那么你也可以用arg1
变换构图
最后一个条件在您的示例中失败,因为 Raw
的处理程序具有类型 Application
,即 Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived
,而另一个处理程序是 ReaderT Foo (ExceptT ServantErr IO)
操作.类型不匹配,因此组合没有 Enter
实例。
有一个 workaround:在您的自定义处理程序上调用 enter
,然后才将它们与 Application
处理程序组合。
type WebApi
= "static" :> Raw
:<|> FooEndpoint
type FooEndpoint = "foo" :> Get '[PlainText] T.Text
readerServer :: ServerT WebApi (ExceptT ServantErr IO)
readerServer = static :<|> enter (readerToHandler "foobarbaz") foo
where
static :: Application
static = undefined
foo :: ReaderT Foo (ExceptT ServantErr IO) T.Text
foo = undefined
我稍微更改了 servant 教程中显示的应用程序,使 Reader
monad 成为 ReaderT
,就像这样
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeOperators #-}
module Lib
( runServer
) where
import Control.Monad.Except
import Control.Monad.Reader
import qualified Data.Text as T
import Network.Wai
import Servant
type WebApi
= "static" :> Raw
:<|> "foo" :> Get '[PlainText] T.Text
type Foo = String
server :: ServerT WebApi (ReaderT Foo (ExceptT ServantErr IO))
server = static :<|> foo
where
static :: Application
static = undefined
-- Handler T.Text
foo :: ReaderT Foo (ExceptT ServantErr IO) T.Text
foo = undefined
webAPI :: Proxy WebApi
webAPI = Proxy
readerToHandler :: Foo -> ReaderT Foo (ExceptT ServantErr IO) :~> ExceptT ServantErr IO
readerToHandler t = Nat (\x -> runReaderT x t)
-- readerServer :: ServerT WebApi (ExceptT ServantErr IO)
-- readerServer = enter (readerToHandler "foobarbaz") server
-- serve' :: Application
-- serve' = serve webAPI server
runServer :: IO ()
runServer = return ()
问题是我无法启用 readerServer
功能,类型检查失败并出现这个难以理解的错误
src/Lib.hs:45:16: error:
• Couldn't match type ‘IO’ with ‘ExceptT ServantErr IO’
arising from a functional dependency between:
constraint ‘Servant.Utils.Enter.Enter
(IO ResponseReceived)
(ReaderT Foo (ExceptT ServantErr IO) :~> ExceptT ServantErr IO)
(IO ResponseReceived)’
arising from a use of ‘enter’
instance ‘Servant.Utils.Enter.Enter (m a) (m :~> n) (n a)’
at <no location info>
• In the expression: enter (readerToHandler "foobarbaz") server
In an equation for ‘readerServer’:
readerServer = enter (readerToHandler "foobarbaz") server
Failed, modules loaded: none.
知道出了什么问题吗?
问题是 Raw
端点的存在,它与 enter
交互不佳。这是Servant中的known annoyance
Enter
类型类决定了可以转换哪些处理程序集,以及使用哪些转换。它有三个实例:
Enter (m a) ((:~>) m n) (n a)
最简单的情况。如果你有一个 monadic 动作和一个将它带到不同 monad 的自然转换,你可以应用转换。Enter b arg ret => Enter (a -> b) arg (a -> ret)
。如果您有一个带参数的处理程序并且您知道如何转换处理程序的最终单子操作,则可以使用相同的arg
转换来转换处理程序。(Enter typ1 arg1 ret1, Enter typ2 arg2 ret2, (~) * arg1 arg2) => Enter ((:<|>) typ1 typ2) arg1 ((:<|>) ret1 ret2)
如果您有一个处理程序组合:<|>
,并且每个处理程序都可以使用 相同 自然转换单独转换arg1
,那么你也可以用arg1
变换构图
最后一个条件在您的示例中失败,因为 Raw
的处理程序具有类型 Application
,即 Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived
,而另一个处理程序是 ReaderT Foo (ExceptT ServantErr IO)
操作.类型不匹配,因此组合没有 Enter
实例。
有一个 workaround:在您的自定义处理程序上调用 enter
,然后才将它们与 Application
处理程序组合。
type WebApi
= "static" :> Raw
:<|> FooEndpoint
type FooEndpoint = "foo" :> Get '[PlainText] T.Text
readerServer :: ServerT WebApi (ExceptT ServantErr IO)
readerServer = static :<|> enter (readerToHandler "foobarbaz") foo
where
static :: Application
static = undefined
foo :: ReaderT Foo (ExceptT ServantErr IO) T.Text
foo = undefined