Tracks
/
Elm
Elm
/
Exercises
/
Ticket, Please!
Ticket, Please!

Ticket, Please!

Learning Exercise

Introduction

Pattern Matching

Pattern matching enables expressive branching code and destructuring brings elegant binding of values to variables.

Simple pattern matching

Pattern matching can bind values to variables, or discard them with the wildcard _, with the use of the case statement.

type Entity = Alien | Stranger (Maybe String) | Friend { name: String }

hello : Entity -> String
hello entity =
    case entity of
        -- Custom type variant
        Alien -> "Hello, you are not from around here are you?"
        -- Binding to a litteral value nested in Stranger
        Stranger (Just nametag) -> "Hello, erm... " ++ nametag ++ "."
        -- Discarding Friend's data
        Friend _ -> "Hi!"
        -- All other cases
        _ -> "Hello stranger!"

Destructuring

Destructuring can binds values in let bindings, function arguments, and of course in case expressions, whenever there is only one shape possible for the data.

pairSum : ( Int, Int ) -> Int
pairSum pair =
    -- Destructuring of a pair in a 'let' binding
    let ( x, y ) = pair
    in x + y

-- Destructuring in the function argument
pairSum : ( Int, Int ) -> Int
pairSum ( x, y ) = x + y

-- Custom type containing a single variant
type Container = Box String

-- Destructuring in the function argument
unbox : Container -> String
unbox (Box str) = str

-- Destructuring combined with pattern matching
unboxMaybe : Maybe Container -> Maybe String
unboxMaybe maybeContainer =
    case maybeContainer of
        Nothing -> Nothing
        Just (Box "42") -> Just "The answer to the universe!"
        Just (Box str) -> Just str

Destructuring can also be used for records, or with the as keyword.

type alias Circle =
    { radius : Float
    , center : ( Float, Float )
    }

perimeter : Circle -> Float
perimeter { radius } =
    2 * pi * radius

left : Circle -> Float
left { radius, center } =
    -- the pair center cannot be pattern matched directly in the function argument
    -- so we do it in a 'let' statement
    let ( x, _ ) = center
    in x - radius

-- using the 'as' keyword to bind both the fields and the whole record
smaller : Float -> Circle -> Circle
smaller reduction ({ radius } as circle) =
    if reduction < radius then
        { circle | radius = radius - reduction }
    else
        circle

Instructions

You are part of the IT helpdesk, and you want to improve the ticketing system to be more efficient and provide statistics for your manager.

A ticket has a status (new, in progress...), a creator (identified by a username and an employee ID), can be assigned to an IT employee and contains a history of comments.

Here are four tasks that wil improve your current ticketing system.

1. Detect empty comments

Empty comments clutter the interface and bring no information, you want to filter them out.

Define a helper function emptyComment that take a user/comment pair and return True for empty comments.

Use destructuring to extract the user and the comment directly in the function argument. Ignore the user since you don't need that information.

emptyComment ( User "Alice", "" )
-- => True

2. Count the number of comments from the creator

Your manager wants to have statistics about user involvement in the ticket process. She asks you to check how many comments are added by the ticket creator.

Define numberOfCreatorComments that takes a ticket and return the number of comments added by the ticket creator.

Use destructuring in the function argument to extract the record from the Ticket type, at the same time as the fields you need from the record. Then, destructure the createdBy in a let binding so you can count the relevant comments. Use List functions to do the count and use destructuring in the function argument of an anonymous function to seperate the user/comment pair.

numberOfCreatorComments
numberOfCreatorComments
    (Ticket
        { status = Closed
        , createdBy = ( User "Chiaki", 1 )
        , assignedTo = Nothing
        , comments = [ ( User "Chiaki", "Never mind, fixed it." ) ]
        }
    )
-- => 1

3. Detect tickets assigned to the Dev Team

Some tickets have to be escalated to Alice, Bob, or Charlie from the Dev Team. It's important to keep an eye on those to make sure they are making progress.

Define a helper function assignedToDevTeam that checks if a ticket is assigned to one of the Dev Team members.

Use destructuring in the function argument to extract the record from the Ticket type as well as the field you need. Then, use a single case statement to check if the ticket is assigned to Alice, Bob, or Charlie using recursive pattern matching to extract their usernames and litteral pattern matching to match them.

assignedToDevTeam
    (Ticket
        { status = InProgress
        , createdBy = ( User "Bill", 2 )
        , assignedTo = Just (User "Alice")
        , comments = [ ( User "Bill", "What's an 'undefined'?" ) ]
        }
    )
-- => True

4. Assign tickets to IT employees

Sometimes a bunch tickets need to be reassigned to other IT employees.

Define a function assignTicketTo to help you do that. The function receives two arguments: a user to assign the ticket to and a ticket. If the status of the ticket is New, assign it to the new user and change the status to InProgress. For other statuses, simply reassign the ticket to the new users, except for tickets with the status Archived that should not be modified at all.

Use destructuring in the function argument to extract the record from the Ticket type as well as the field you need, and also use the as keyword to capture the whole record as it is. Use a single case statement to pattern match the status and return the appropriate ticket, created from modifying the original record you captured with the as keyword.

assignTicketTo (User "Danny")
    (Ticket
        { status = New
        , createdBy = ( User "Jesse", 3 )
        , assignedTo = Just (User "Alice")
        , comments = [ ( User "Jesse", "I've been waiting for 6 months!!" ) ]
        }
    )
-- => Ticket
--        { status = InProgress
--        , createdBy = ( User "Jesse", 3 )
--        , assignedTo = Just (User "Danny")
--        , comments = [ ( User "Jesse", "I've been waiting for 6 months!!" ) ]
--        }
Edit via GitHub The link opens in a new window or tab
Elm Exercism

Ready to start Ticket, Please!?

Sign up to Exercism to learn and master Elm with 22 concepts, 85 exercises, and real human mentoring, all for free.