Gatonito
Gatonito

Reputation: 2174

What does something :: Wrapper type mean in Haskell?

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

Answers (3)

Jon Purdy
Jon Purdy

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 returns Reader 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

Random Dev
Random Dev

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

Mark Saving
Mark Saving

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

Related Questions