Futarimiti
Futarimiti

Reputation: 685

Any difference between `Reader a (b -> c)` and `b -> Reader a c`?

(Some other mtl monads would bring up the same problem, but let's just use Reader here to exemplify)

Say I need to show something in a human-readable way, with the implementation and behaviour details depending on the user preferences, read from a configuration:

showIt :: Config -> Object -> String

Where the user configuration is merely read and not to be modified, so might as well use Reader to reflect that:

showIt :: Reader Config (Object -> String)

But wait, how is that different from this:

showIt :: Object -> Reader Config String

Though I hardly know Reader, I do have tried both designs and they seem functionally equivalent. I still wonder if there would be a semantic difference in between, that is, when some other Haskellers look at my code, would they interpret a different purpose or meaning if I had used the other design instead? If so, then which usage should be preferred in scenarios like this?

Upvotes: 3

Views: 132

Answers (2)

Benjamin Hodgson
Benjamin Hodgson

Reputation: 44634

On the contrary to the other answer, these are all the same thing.

Reader a b is just a newtype wrapper for a -> b,

newtype Reader a b = Reader (a -> b)

meaning that Reader a b has exactly the same capabilities as a -> b - it's just a different API. And since a -> b -> c is just another way of writing a -> (b -> c), you can see that these are all just different ways of writing the same thing:

Reader Config (Object -> String)
Config -> (Object -> String)
Config -> Object -> String

Your last example is still morally the same type as the others. The order in which a function accepts its arguments is immaterial - you could just as easily have written it the other way around:

Object -> Reader Config String
Object -> (Config -> String)
Object -> Config -> String
Config -> Object -> String  -- flip the arguments

Code using Reader happens to be written in a certain style (that is, using do notation, runReader, and friends), but that's really just a distraction - it's the behaviour which we actually care about.

There certainly are other (non-Reader) monads for which these types are not equivalent, but that's a different question!

Upvotes: 0

Bergi
Bergi

Reputation: 665276

How is Reader Config (Object -> String) different from Object -> Reader Config String?

The former limits the showIt implementation to read a config only once, and then produce a string conversion function that must work for all objects. The latter allows the implementation to decide what config to read (or rather, whether to read1) depending on the input object.

Their usage patterns are quite different:

do
  showAny <- showIt
  let str1 = showAny object1
  let str2 = showAny object2
  return (str1 ++ " " ++ str2)

vs

do
  let show1 = showIt object1
  let show2 = showIt object2
  str1 <- show1
  str2 <- show2
  return (str1 ++ " " ++ str2)

1: with the Reader monad, there is no much choice - you can only decide whether to read the config or not (or: how often to read it - but it'll be the same every time). With other monads, where reading different bits of the configuration might involve different file system or database accesses etc, the difference will become larger.

Upvotes: 2

Related Questions