Implementation

Test #1 — Character name, surname and gender

Feature 1— Type aliases

Now that we have our tests we can start implementing our character!

For our schema we’re going to use a type alias . It allows us to have a common name for any other type. We’re going to use it to alias a struct. Which Elmchemy represents as a map with atoms as keys.

Create a elm/Character.elm file with our type alias declaration.

elm/Character.elm

Type Alias Tip: If the type we’re aliasing is a struct or a tuple, we can use the alias as a function to create an instance of it with each argument being a each subsequent value in our type. f.i Character name surname gender health

We declare our Character to have a name and a surname, which are strings.

A gender of type Gender which is a type we didn’t declare yet, and health as tuple, where the first one is current health, and the latter is max health.

Feature 2— Union Types

Whenever we wan’t something to be matched on, we want a custom type.

In Elmchemy types are so called ‘Tagged unions’ which basically means, the first symbol in each type declaration is a ‘tag’ that tells us what type the value represents. Union types in Elmchemy are represented as atoms in case of a single tag, and a tuple starting with an atom in case of tags wrapping one or more type.

That’s how we’re going to declare our gender

elm/Character.elm — We declare that gender can either be an atom :male, :female or :other

Feature 3— Type Aliases as functions

Now we can declare a new function that returns a new character based on it’s parameters.

elm/Character.elm — function returning our Character type alias intance

We take 3 parameters, which are name and surname as strings and a gender as our newly created Gender union type. Then we pass the arguments to Character type as a function in the order we defined the fields.

We also pass the default argument of health being 100 on 100 max.

If we run mix test in our terminal right now we should have our first test passing. 3 to go!

Test #2 — Character stats

Time to add stats. Let’s recall our test case

character_test.exs

First we need to define stats for our character type.

And also define the stats structure:

When we save, our compiler should nag us, that our new/3 function is no longer relevant to our type.

Lets update it to contain default stats all being of value 0

Feature 4 — case statement on union types and record update syntax

We need a type of a stat to match on

Now we need to declare a function that takes a stat, value and character, and updates the stat to the value we want.

Did you know?

You can define type aliases using record update syntax.

type alias Namable a = { a | name : String}

means any structure that has a field name of type String .

You can then create types deriving from it like so:

type alias Cat = Namable { lactoseIntolerance : Bool }

type alias Dog = Namable { catIntolerance : Int }

But remember, that way you sacrifice the short syntax for instantiating type aliases and have to type the entire struct by hand.

Great. That’s another test down!

Test #3 — Boosting vitality

test/character_test.exs — We check that max hp increased, and also that our current hp adjusted itself accordingly

Now we need to add the functionality of changing health when setting a vitality stat.

Feature 5— Operators as functions and custom operators

Because our HP is a tuple we want to use Tuple.map on it. But for the sake of code tidiness we’re going to define an operator for us to use that as infix operator.

We could use any operator i like, but for the sake of the shape I chose <$ for the left tuple element and $> for the right one. You can experiment with what works best for you.

Did you know?

There is plenty of built in operators to make your life easier. You can use |> and <| to pipe function results, << and >> to compose functions. There even is a comma operator that creates tuples for you.

(,) a b is equivalent to (a, b)

(,,) a b c to (a, b, c) and so on.

Every operator is still a function so we can pass them to other functions to make our code much more expressive.

All it takes to implement a zip of two lists is just to write

List.map2 (,) listA listB

Great. Now we can add the health change to the Vitality branch of our setStat case.

We use <$ to add a difference of current vitality and a new vitality value multiplied by 10.

Then we use $> to set it to base 100 plus 10 times the vitality value.

Since we need to pass it a function in the first case we give it a (+) function and in the second we give it always which basically returns always the same thing (Basically it means the same as \_ -> (100 + 10 * value) )

If we didn’t use our operator the code would look as follows

character.health

|> Tuple.mapFirst ((+) ((value - character.stats.vitality) * 10))

|> Tuple.mapSecond (always (100 + 10 * value))

By now we should be with only one test failing left.

Test #4 — Equipping a weapon when we’re intelligent enough

test/character_test.exs - a weapon with 9 intelligence requirement,

So we need a function, that either can or cannot equip a weapon. To do that we’re going to use Result a b type, that have a value of either

Err a or Ok b , which directly translates to {:ok, a} or {:error, b} .

Please notice the difference of Err translating to :error instead of :err . That’s one of exceptions in Elmchemy to keep Elixir and Elm interoperable without a consistency sacrifice

But before we do that, we need to implement a Weapon type in general. To do that we’re create a new file, and learn to import and use remote types.

Feature 6 — Union types and Type Aliases imported from other modules

Let’s create a new elm/Weapon.elm file and add a new function that would create a weapon for us

elm/Weapon.elm — weapon type definition

*Please note that we create a new function only for the sake of usage inside our Elixir code. The Weapon type alias is a factory function itself when used inside Elmchemy’s scope. Character factory function ( Character.new ) was different because we specified some default values for it*

Now that we have our Weapon type we can implement our equip function, but first we need to add an import on the top of our Character.elm file,

we use import Weapon exposing (Weapon) because the first one is the name of the module, and the latter of the type alias we want to import.

Also our character is right now armless. Let’s add it an arm, of type Maybe Weapon . Like we decided earlier we want to use Maybe type, because our character can either have something in his hand or have nothing at all.

Let’s add an arm to our Character type with

Did you know?

There is a shorthand syntax to access a field in a struct as a function.

By writing .field you can have a function that will fetch field of a record. It’s equivalent to \a -> a.field

For example:

arsenal : List Character -> List Weapon

arsenal squad = List.map .arm squad

We need to add a default value for arm to our Character.new function too.

elm/Character.elm

Now that we have the type imported from the other file and an arm for our character we can go about our weapon equipping implementation:

elm/Character.elm

That’s it! If we run the tests now, we should see all 4 tests green.

If you skipped some parts you can see the entire project under this repository:

https://github.com/wende/elmchemy-article-example

This is the end of part two

In part three we will focus on calling Elixir code from Elmchemy and writing your own Native modules.

In case of any questions regarding the project I’ll gladly answer in the comments section.

Announcement:

Elmchemy is undergoing a name change and is soon to be called “Elchemy” (without an “m”)

<<< Part One —

— Part Three >>>