# Everything is a Value

What is a value? We're going to avoid the philosophical kind of values for now and focus on the computer science kind.

According to Wikipedia:

In computer science, a value is the representation of some entity that can be manipulated by a program.

Normally when we think of values we think of *primitive* values, like numbers or strings. We can perform operations on these values, such as adding two numbers together or concatenating two strings together to produce a new value:

ghci> 1 + 1 2 ghci> 2 + 2 4 ghci> "Hello" ++ ", world!" "Hello, world!"

Numbers, in particular, have some interesting properties afforded to them by the laws of mathematics, such as the associative property and the commutative property:

ghci> (1 + 1) + 2 == 1 + (1 + 2) True ghci> 3 + 5 == 5 + 3 True

In this post we're going to explore treating complex types the same way we treat values, and what happens when we do so.

## MMO-tivating Example

Let's pretend we're creating a fantasy MMORPG and we want to track some basic statistics about each player. We'll define a record to hold the stats that we're interested in tracking:

data PlayerStats = PlayerStats { hoursPlayed :: Float , monstersKilled :: Int , goldEarned :: Int } deriving (Eq, Show)

At some point we'll end up with a list of `PlayerStats`

, probably retrieved from a database or some other means of persistence. That might look something like this:

allPlayerStats :: [PlayerStats] allPlayerStats = [ PlayerStats {hoursPlayed = 2.5, monstersKilled = 25, goldEarned = 39} , PlayerStats {hoursPlayed = 14, monstersKilled = 167, goldEarned = 543} , PlayerStats {hoursPlayed = 7.75, monstersKilled = 125, goldEarned = 234} -- ... ]

Now suppose we want to aggregate these statistics to get the totals across all players. One approach to this might be to write a function to sum each stat individually:

totalHoursPlayed :: [PlayerStats] -> Float totalHoursPlayed = foldr (+) 0. map hoursPlayed totalMonstersKilled :: [PlayerStats] -> Int totalMonstersKilled = foldr (+) 0 . map monstersKilled

While this approach works, it isn't exactly ideal. For one, we need to define a new function each time we have a new stat that we want to aggregate. Notice how we don't have a `totalGoldEarned`

function defined. If we wanted to get the total amount of gold earned across all players we would need to write a `totalGoldEarned`

function.

Another shortcoming of this approach is that each time we calculate one of these stats we need to traverse the entire list of player stats.

We can do better!

Let's write a function that will sum all of our player stats:

sumPlayerStats :: [PlayerStats] -> PlayerStats sumPlayerStats = foldr (\a b -> PlayerStats { hoursPlayed = hoursPlayed a + hoursPlayed b , monstersKilled = monstersKilled a + monstersKilled b , goldEarned = goldEarned a + goldEarned b }) PlayerStats {hoursPlayed = 0, monstersKilled = 0, goldEarned = 0}

This looks much better. Now we can use `sumPlayerStats`

to run through the list of stats *once* and then pull out whichever of the accumulative stats that we want:

ghci> sumPlayerStats $ allPlayerStats PlayerStats {hoursPlayed = 24.25, monstersKilled = 317, goldEarned = 816} ghci> hoursPlayed . sumPlayerStats $ allPlayerStats 24.25

While this approach is perfectly fine, it turns out we can still do better.

Let's take a step back and return to the concept of values.

Say we want to sum a list of numbers. For example, here's how we could sum the numbers from one to ten:

ghci> foldr (+) 0 $ [1..10] 55

To sum a list of numbers we're still using `(+)`

, just like when we performed `1 + 1`

or `2 + 2`

. Can we make summing our `PlayerStats`

just as simple as `1 + 1`

?

Turns out, we can!

The first thing we'll need to do is define an instance of `Num`

for `PlayerStats`

:

instance Num PlayerStats where a + b = PlayerStats { hoursPlayed = hoursPlayed a + hoursPlayed b , monstersKilled = monstersKilled a + monstersKilled b , goldEarned = goldEarned a + goldEarned b } a - b = PlayerStats { hoursPlayed = hoursPlayed a - hoursPlayed b , monstersKilled = monstersKilled a - monstersKilled b , goldEarned = goldEarned a - goldEarned b } a * b = PlayerStats { hoursPlayed = hoursPlayed a * hoursPlayed b , monstersKilled = monstersKilled a * monstersKilled b , goldEarned = goldEarned a * goldEarned b } abs stats = PlayerStats { hoursPlayed = abs $ hoursPlayed stats , monstersKilled = abs $ monstersKilled stats , goldEarned = abs $ goldEarned stats } signum stats = PlayerStats { hoursPlayed = signum $ hoursPlayed stats , monstersKilled = signum $ monstersKilled stats , goldEarned = signum $ goldEarned stats } fromInteger i = PlayerStats { hoursPlayed = fromInteger i , monstersKilled = fromInteger i , goldEarned = fromInteger i }

While this might seem like a lot of code at first, I promise it is all worth it in the end. By defining `Num`

for our `PlayerStats`

record we've given it the ability to behave just like a number.

With that in place, look at what our `sumPlayerStats`

from before turns into:

ghci> foldr (+) 0 $ allPlayerStats PlayerStats {hoursPlayed = 24.25, monstersKilled = 317, goldEarned = 816}

It's identical to when we summed the numbers from one to ten! By treating `PlayerStats`

like a number we get all the benefits of a number, like being able to combine two instances using `(+)`

.

One thing to note is that this does not always work. The reason we can do this with `PlayerStats`

is because it is comprised solely of numeric fields.

So now we know how to treat records as values. Are there other things that we can treat as values?

## Fun with Functions

Let's define an `isDivisibleBy`

function that checks whether some integer is divisible by another integer:

isDivisibleBy :: Int -> Int -> Bool isDivisibleBy divisor n = n `mod` divisor == 0

We can then implement `isEven`

in terms of `isDivisibleBy`

, like so:

isEven :: Int -> Bool isEven = isDivisibleBy 2

With these two functions we can implement an `isDivisibleBy6`

function using the divisibility rules:

isDivisibleBy6 :: Int -> Bool isDivisibleBy6 n = isEven n && isDivisibleBy 3 n

We could also have implemented

`isDivisibleBy6`

just by doing`isDivisibleBy 6`

, but we're doing it this way in order to identify a more general pattern.

Let's examine `isDivisibleBy6`

a little more closely. Notice how we have two terms: `isEven n`

and `isDivisibleBy 3 n`

. When the function is executed we'll evaluate one or both terms (`(&&)`

in Haskell short-circuits if the first term is `False`

) and then use `(&&)`

to compute the result.

Notice how we have an `n`

on both sides of the "equation". On one side we take `n`

as an argument to the function, and on the other we apply `n`

to both terms.

Time for a brief detour. If we were to put this into mathematical terms, it's like having:

4 + x = (1 + x) + (3 + x)

We can remove all of the `x`

s from the equation and the equation will still hold true.

Returning to our `isDivisibleBy6`

function, can we perform the same simplification here?

Let's try just removing `n`

entirely and `(&&)`

ing our two functions together:

ghci> isEven && isDivisibleBy 3 $ 6 <interactive>:12:1: error: • Couldn't match expected type ‘Bool’ with actual type ‘Int -> Bool’ • Probable cause: ‘isEven’ is applied to too few arguments In the first argument of ‘(&&)’, namely ‘isEven’ In the expression: isEven && isDivisibleBy 3 In the expression: isEven && isDivisibleBy 3 $ 6 <interactive>:12:1: error: • Couldn't match expected type ‘Integer -> t’ with actual type ‘Bool’ • The first argument of ($) takes one argument, but its type ‘Bool’ has none In the expression: isEven && isDivisibleBy 3 $ 6 In an equation for ‘it’: it = isEven && isDivisibleBy 3 $ 6 • Relevant bindings include it :: t (bound at <interactive>:12:1) <interactive>:12:11: error: • Couldn't match expected type ‘Bool’ with actual type ‘Int -> Bool’ • Probable cause: ‘isDivisibleBy’ is applied to too few arguments In the second argument of ‘(&&)’, namely ‘isDivisibleBy 3’ In the expression: isEven && isDivisibleBy 3 In the expression: isEven && isDivisibleBy 3 $ 6

Well, that didn't work out so well. The compiler is complaining because `(&&)`

only works on `Bool`

s, and we're trying to use it with an `Int -> Bool`

.

But what if we could make a version of `(&&)`

that knew how to work with an `Int -> Bool`

?

We can define our own logical operators that work on unary functions that return `Bool`

:

(&&&) :: (a -> Bool) -> (a -> Bool) -> (a -> Bool) (&&&) f g = \a -> f a && g a infixr 3 &&& (|||) :: (a -> Bool) -> (a -> Bool) -> (a -> Bool) (|||) f g = \a -> f a || g a infixr 3 |||

I couldn't find a way to overload

`(&&)`

and`(||)`

for functions, hence why we need to use slightly different operators for them.

Now let's try out the expression we tried before, but this time using our own `(&&&)`

operator in place of `(&&)`

:

ghci> isEven &&& isDivisibleBy 3 $ 6 True

It works!

And here's what it would look like as a function declaration:

isDivisibleBy6' :: Int -> Bool isDivisibleBy6' = isEven &&& isDivisibleBy 3

We've now simplified the "equation" by removing the redundant `n`

from both sides. What enabled us to do this was treating our functions as values and being able to combine them using logical operators.

While this particular example might appear to be of limited use (after all, we can just do `isDivisibleBy 6`

instead), it does open the door to more possibilities.

It's time to use our imaginations again! This time pretend that we're running a streaming service. Our domain might look something like this:

data Viewer = Viewer { age :: Int , isSubscriber :: Bool , hasParentalApproval :: Bool } is18OrOlder :: Viewer -> Bool is18OrOlder viewer = age viewer >= 18

Let's say that we have the following business requirement:

"In order to be eligible for a giveaway a viewer must be a subscriber and either 1) be 18 or older or 2) have parental approval."

Using our `(&&&)`

and `(|||)`

operators we can define a function that expresses this business requirement just as clearly as it was stated above:

isEligibleForGiveaway :: Viewer -> Bool isEligibleForGiveaway = isSubscriber &&& (is18OrOlder ||| hasParentalApproval)

We can also test out our `isEligibleForGiveaway`

function to ensure it's working as expected:

ghci> isEligibleForGiveaway $ Viewer {age = 19, isSubscriber = True, hasParentalApproval = False} True ghci> isEligibleForGiveaway $ Viewer {age = 21, isSubscriber = False, hasParentalApproval = False} False ghci> isEligibleForGiveaway $ Viewer {age = 11, isSubscriber = True, hasParentalApproval = True} True

You can see how as the business requirements change and new requirements are added it would be trivial to update our `isEligibleForGiveaway`

function to incorporate the new requirements while still remaining readable and easy to reason about.

## Wrapping Up

In this post we've explored how we can treat complex types—like records and functions—as values, and gain a number of benefits in doing so.

By treating our `PlayerStats`

record like a number we were able to sum a list of stats just as easily as we could with a list of `Int`

s.

And by treating our functions as values we discovered that we could combine functions together using logical operators in order to clearly express business requirements in code.

I hope you enjoyed reading this, and hopefully learned something new that you can use in your day-to-day programming.

If you liked this post and want to get notified about new ones like it, be sure to subscribe to my newsletter, Errata Exist.

And if you have any questions, comments, or other feedback, please get in touch with me. I'd love to hear from you!