Reputation: 2174
I thought everything were functions in Haskell. For example:
sum :: Int -> Int
sum a b = a+b
But there are things like this:
tom :: Reader String String
what does this mean exactly? What is tom
? A function that gets nothing and returns Reader String String
?
Upvotes: 2
Views: 385
Reputation: 55039
I thought everything were functions in Haskell.
Nope, in Haskell, a value is only a function if its type is x -> y
for some types x
and y
.
Note that all functions in Haskell have exactly one input and one output. We represent functions of multiple inputs using currying e.g.:
x :: A
y :: B
z :: C
f :: A -> B -> C -> D
f x :: B -> C -> D
f x y :: C -> D
f x y z :: D
-- With explicit parentheses:
f :: A -> (B -> (C -> D))
f x :: B -> (C -> D)
(f x) y :: C -> D
((f x) y) z :: D
Casually, we would say this function takes 3 arguments, but this is just another way of thinking about the chain of unary functions; in reality, it can take up to 3 arguments—or even more, if D
is a function type!
tom :: Reader String String
what does this mean exactly?
tom
is a variable, or a binding, which is bound to a value of type Reader String String
. This represents an action in the Reader String
context. tom
is just like a variable of any other type, including data types like Int
or String
:
count :: Int
count = length beans
beans :: String
beans = "beans"
Or other “action” types like IO String
:
getLength :: IO String
getLength = do
line <- getLine
pure (length line)
What is
tom
? A function that gets nothing and returnsReader String String
?
It’s an action in the Reader String
context; it represents a computation which may use a read-only value of type String
(the first one) to produce a result of type String
(the second).
It’s not a plain function itself, but incidentally a Reader
action can be implemented as a newtype
which just contains a function:
newtype Reader r a = Reader (r -> a)
runReader :: Reader r a -> r -> a
runReader (Reader f) = f
So runReader tom
is a function of type String -> String
. Reader
allows you to avoid explicitly adding parameters to all of your functions that share some “configuration” or “environment” value (serving one of the roles of “dependency injection” in imperative/OOP languages).
Normally, when a type constructor is an instance of Monad
(and therefore also Applicative
and Functor
), we refer to concrete values of that type as “actions”; some of these values, like [a]
, Maybe a
, and Either e a
, are also “containers” of some kind. But others, like Reader r a
, State s a
, and IO a
, are only actions—they don’t really have a “container” interpretation, they’re just descriptions of operations. You can combine these operations in a generic way with >>=
/ <*>
/ pure
/ <$>
, but how you “run” an action depends on the particular type.
For Reader
and State
, there are runReader
and runState
functions, but for IO
, there is no such function. Your Haskell code constructs an IO ()
value (without side effects!) and the Haskell runtime evaluates this value for you if you assign it to main
. This is the sense in which Haskell is “purely functional”.
In other words, an IO String
isn’t a String
“tagged” with the fact that it came from IO
, it’s a procedure for producing a String
, which is why you can’t “get the a
out of an IO a
”.
Upvotes: 3
Reputation: 52290
You can probably see this as a function with no parameter - on some level that works ... for a while.
But a better answer is probably that no - not everything in Haskell is a function - there are just values as well and I'd call this a value of type Reader String String
.
Same with "Hello"
or 5
remark incidentally Reader
is only a wrapper around a function Reader a b ~ a -> b
;)
Upvotes: 5
Reputation: 1797
Not everything is a function. For example,
x :: Char
x = 'w'
In the above example, x
is not a function.
As for Reader String String
, this is just a newtype
alias of String -> String
. To convert between the two, we have
runReader :: Reader a b -> (a -> b)
reader :: (a -> b) -> Reader a b
One can do nice things with Reader
. For example, consider
average :: [Double] -> Double
average list = sum list / fromIntegral (length list)
Notice that we're passing list
into two different functions. On the other hand, we can instead write
average :: [Double] -> Double
average = runReader $ do sumList <- reader sum
lengthList <- reader length
return (sumList / fromIntegral lengthList)
With the second definition of average, we can use the reader monad to eliminate the use of the list
parameter entirely, instead using monadic do
notation to eliminate the need for it.
Whether this makes code easier to understand or harder is an interesting question.
Upvotes: 7