zoran119
zoran119

Reputation: 11327

How to carry a value across function call chains

Let's say I have the following data structure and functions:

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

I'd like to be able to chain getUserId and hasPermission with some syntactic sugar without having to carry instance of Settings as I chain the function calls. Something like this:

main = do
  let _permission = Settings{dbName="prod"} >>= getUserId "bob" >> hasPermission
  print _permission

This (obviously) does not work.

Any go-to patterns for this this?

Upvotes: 4

Views: 119

Answers (2)

luqui
luqui

Reputation: 60543

There is a feature called implicit parameters, which has somewhat gone out of style lately, but I believe it is still supported, which offers a nice solution for this.

{-# 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")

See also the implicit configurations paper, which explores this problem in quite some depth, and its corresponding library, reflection.

Upvotes: 0

Mark Seemann
Mark Seemann

Reputation: 233487

The simplest way to address such concerns is, in my opinion, to use partial application, like this:

main = do
  let settings = Settings { dbName="prod" }
  let getUserId' = getUserId settings
  let hasPermission' = hasPermission settings
  let _permission = hasPermission' $ getUserId' "bob"
  print _permission

If you put the 'common' argument last, however, you can also use the built-in reader monad instance:

main :: IO ()
main = do
  let getPermission = (flip getUserId) "bob" >>= (flip hasPermission)
  print $ getPermission $ Settings { dbName="prod" }

Here getPermission is a local function with the type Settings -> Bool. Typically, I consider the first option (partial application) to be simpler and easier to understand.

Upvotes: 1

Related Questions