Reputation: 685
(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
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
Reputation: 665276
How is
Reader Config (Object -> String)
different fromObject -> 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