假设我有以下数据结构和功能:
data Settings = Settings { dbName :: String } deriving Show
-- username to user id lookup
getUserId :: Settings -> String -> Int
getUserId settings username = 1
-- checks permission for a given user id
hasPermission :: Settings -> Int -> Bool
hasPermission settings userId = True
我希望能够用一些语法糖链接getUserId
和hasPermission
而不必携带Settings
的实例,因为我链接函数调用。像这样的东西:
main = do
let _permission = Settings{dbName="prod"} >>= getUserId "bob" >> hasPermission
print _permission
这(显然)不起作用。
这个的任何转变模式?
在我看来,解决此类问题的最简单方法是使用部分应用程序,如下所示:
main = do
let settings = Settings { dbName="prod" }
let getUserId' = getUserId settings
let hasPermission' = hasPermission settings
let _permission = hasPermission' $ getUserId' "bob"
print _permission
但是,如果你将'common'参数放在最后,你也可以使用内置的阅读器monad实例:
main :: IO ()
main = do
let getPermission = (flip getUserId) "bob" >>= (flip hasPermission)
print $ getPermission $ Settings { dbName="prod" }
这里getPermission
是Settings -> Bool
类型的本地函数。通常,我认为第一个选项(部分应用程序)更简单,更容易理解。
有一个名为implicit parameters的功能,最近有点不合时宜,但我相信它仍然受到支持,这为此提供了一个很好的解决方案。
{-# LANGUAGE ImplicitParams #-}
data Settings = Settings { dbName :: String } deriving Show
getUserId :: (?settings :: Settings) => String -> Int
getUserId username = 1
hasPermission :: (?settings :: Settings) => Int -> Bool
hasPermission userId = True
main = do
let ?settings = Settings { dbName = "prod" }
print $ hasPermission (getUserId "bob")
另请参阅implicit configurations论文,它深入探讨了这个问题,以及相应的库reflection
。