Chapter 8


Tips ⚠️️

  • Use try.purescript.org to test out the code examples in this book.
  • Whenever the example code starts with module Main where, make sure to clear out the code editor on try.purescript.org before pasting new code in. This will help to avoid unncessary errors

Case statements

It's time to introduce a very important concept; we'll call it decision making. Up to this point, we've worked with various types of input, but we haven't really done anything interesting with that data. Our programs run straight through from start to finish with the same behavior regardless of the data it gets. What we really want is to have a program that does different things based on what we give it!
For example, we might write a program that greets the user differently based on their age, saying "Hello child!" to ages below 18 and "How do you do?" to those above. Another example might be a prize received for acheiving a certain grade in school. Perhaps C grades and lower earn pencils, B grades earns you a candy, and A grades will get you a hamburger! We can write programs that do the above using case statements, let's look at some examples.

To start, we'll stay simple and use our CoolType defined in the previous chapter. Using this type, we'll output a different string based on which CoolType value we're using.

module Main where
import Prelude
import Debug

data CoolType
  = Cool
  | VeryCool
  | SuperCool

-- | Returns a cool string message based on the `CoolType` value
getCoolMessage :: CoolType -> String
getCoolMessage coolVar = case coolVar of
  Cool -> spy "msg" "Cool..."
  VeryCool -> spy "msg" "That seems... very cool..!"
  SuperCool -> spy "msg" "You couldn't be cooler"

message1 :: String
message1 = getCoolMessage VeryCool

message2 :: String
message2 = getCoolMessage SuperCool

If you check the developer console, you should see the following output:

msg: That seems... very cool..!
msg: You couldn't be cooler

The above example shows nearly everything there is to see regarding the basics of case statements. Your intuition may be enough to understand what's happening, but before we break down the example further, let's look at the case statement a little more closely.

Case Basics: The case variable

The syntax for our case statement is as follows

case value of
  case1 -> response1
  case2 -> response2
  ...

The first part is case value of. We can put anything with a value between the case and the of keywords (taking the place of the word value above), for example:

  • A top-level variable
  • A function parameter
  • An actual function call which returns a value
  • Any let or where bindings which yeild a value.
module Main where
import Prelude

data Decision
  = This
  | That

-- 1. Top-level variable
someDecision :: Decision
someDecision = This

topLevelCase :: Int
topLevelCase = case someDecision of
  This -> 10
  That -> 20

-- 2. A function parameter
caseFunction :: Decision -> String
caseFunction decision = case decision of
  This -> "This thing"
  That -> "That thing"

-- 3. A function call
-- We ignore the `num` parameter and just return a `That`
someFunction :: Int -> Decision
someFunction num = That

functionCallCase :: Boolean
functionCallCase = case someFunction 20 of
  This -> false
  That -> true

-- 4. Let/Where bindings in a case
letCase :: String
letCase =
  let
    letVar = That
  in
    case letVar of
      This -> "Hello"
      That -> "Goodbye"

whereCase :: Int
whereCase = case whereVar of
  This -> 20
  That -> 100
  where
  whereVar = This

Case Basics: The case response

The next part to turn our attention to is:

case1 -> response1
case2 -> response2

That is: some value, followed by an -> followed by some sort of response value. What are the values case1, case2, etc? These correspond to the values associated with our type; we'll have as many cases as we do values for that type.

module Main where
import Prelude

--
-- 1. A single value type --
data Boring
  = Boo

-- There's only one case value to work with here: `Boo`.
ex1 :: Boring -> Boolean
ex1 var = case var of
  Boo -> true

--
-- 2. A type with 2 valid values --
data Decision
  = This
  | That

-- We have two valid values for the Decision type, so we have two cases to deal with
ex2 :: Decision -> Boolean
ex2 decision = case decision of
  This -> true
  That -> false

--
-- 3. A Type with three valid values
data CoolType
  = Cool
  | VeryCool
  | SuperCool

-- Our Original CoolType has 3 valid values, so we have three cases to deal with now
ex3 :: CoolType -> Int
ex3 coolVal = case coolVal of
  Cool -> 10
  VeryCool -> 90
  SuperCool -> 9001 -- It's over 9000!

Back to the examples

Ah so now that we've been through a basic run down of case statements, we return to our original example.

module Main where

import Prelude
import Debug

data CoolType
  = Cool
  | VeryCool
  | SuperCool

-- | Returns a cool string message based on the `CoolType` value
getCoolMessage :: CoolType -> String
getCoolMessage coolVar = case coolVar of
  Cool -> spy "msg" "Cool..."
  VeryCool -> spy "msg" "That seems... very cool..!"
  SuperCool -> spy "msg" "You couldn't be cooler"

Hopefully reading this makes a lot more sense now. We check the three possible values we could encounter for the CoolType type, and use spy to output the appropriate message to the console.

We can also use case statements on the primitive types.

module Main where

import Prelude
import Debug

-- | Write to the console a string and return it based on a Boolean (true/false)
areYouCool :: Boolean -> String
areYouCool bool = case bool of
  true -> spy "msg" "I knew you were!"
  false -> spy "msg" "Aw cmon, you're cooler than you think!"

It's also possible to use case statements on bigger types, like Int or String, but we aren't equipped to deal with that just yet. Case statements rely on Pattern Matching to inspect the values, and we've only used the simplest of pattern matches here. In the next chapter, we'll go into a deeper exploration of Pattern Matching to learn how to handle a couple of the more complicated and common scenarios.

Finally, one last thing to take note of: Because case statements yield a value just like a variable or a function, all the different response values of our case statement must be the same type. If you try to use a different type in each response, you'll be met with an error.

module Main where
import Prelude

-- This function won't compile
badCaseFunc :: Boolean -> String
badCaseFunc bool = case bool of
  true -> "Hello!"
  false -> 54

The above is wrong because the case statement tries to return a String in the true case but an Int in the false case (Also because badCaseFunc should return a String in general).

Summary

We use case statements to inspect the values of a type, allowing us to react in different ways to the different possibilites.

The syntax of a case statement takes the general form

case value of
  case1 -> response1
  case2 -> response2
  ...

Where

  • value is some value (like a variable)
  • Each case covers a different possible value for value
  • Each response following the -> gives us a way act differently based on each case.
  • And each response must be of the same type

Here's an example of a simple case statement

module Main where
import Prelude

data Greeting
  = Hello
  | Hi
  | Yo

responseToGreeting :: Greeting -> String
responseToGreeting greeting = case greeting of
  Hello -> "Hello to you"
  Hi -> "Hey!"
  Yo -> "Yo yo yo!"

Self Practice

1. Using the following type:

data LetterGrade
  = A
  | B
  | C
  | D
  | F

Write a function called prize which outputs a string to the console telling the user what prize they have won for their efforts. Create 2 variables and assign them the result of our prize function so you can see it work in the console.

2. Using the following type:

data BetterBool
  = Yes
  | No

Create a function called upgradeBool which takes a Boolean and turns it into a BetterBool. Next, create a function called downgradeBool which takes a BetterBool and turns it back into a Boolean.


Answers

Question 1.

module Main where
import Prelude
import Debug

data LetterGrade
  = A
  | B
  | C
  | D
  | F

prize :: LetterGrade -> String
prize grade = case grade of
  A -> spy "A Prize" "In N Out"
  B -> spy "B Prize" "Little Ceasars"
  C -> spy "C Prize" "2oz cup frozen yogurt"
  D -> spy "D Prize" "Jolly Rancher"
  F -> spy "F Prize" "One potato chip"

reward1 :: String
reward1 = prize B

reward2 :: String
reward2 = prize F

Question 2.

module Main where
import Prelude

data BetterBool
  = Yes
  | No

upgradeBool :: Boolean -> BetterBool
upgradeBool bool = case bool of
  true -> Yes
  false -> No

downgradeBool :: BetterBool -> Boolean
downgradeBool better = case better of
  Yes -> true
  No -> false