Test Driven Development in Functional Language Elm (Chapter 5-7)

This is a continuation of the previous article (https://qiita.com/ababup1192/items/d6ca1af6efcbac8d550f). Last time I checked the strength of Elm's data types (especially equivalence comparisons). How about this time's content? Let's take a look.

Chapter 5

Until now, the only currency we could handle was the US dollar, but let's add Franc. In addition, there are corrections in the part pointed out in the previous comment. If you write `module Dollar exposing (..) ```, you cannot actually rewrite Amount, but you can get it, so it is not private. You can make it private if you write module Dollar exposing (Dollar) , but then you will not be able to write a value constructor like ``` Dollar 5. .. To avoid this, you need to expose the `` `dollar: Int-> Dollar so-called factory pattern function. As I read this book, the factory pattern will appear in a later chapter, so I will go with a public (non-rewritable) amount up to that point.

-[x] \ $ 5 + 10 CHF = \ $ 10 (when the rate is 2: 1)

First, let's write only the test. Of course it is a compilation error. At this point, a simple currency comparison, as we saw last time, is language guaranteed and is not included in the test case. Since times is covered by the name, it is used properly in the Dollar and Franc modules.

tests/Test.elm

all : Test
all =
    describe "Money Test"
        [ describe "Dollar"
            [ "Multiplication1"
                => (Dollar 5 |> Dollar.times 2)
                === Dollar 10
            , "Multiplication2"
                => (Dollar 5 |> Dollar.times 3)
                === Dollar 15
            ]
        , describe "Franc"
            [ "Multiplication1"
                => (Franc 5 |> Franc.times 2)
                === Franc 10
            , "Multiplication2"
                => (Franc 5 |> Franc.times 3)
                === Franc 15
            ]
        ]

As you can see in this book, it's very painful, but let's copy and paste the Dollar definition to implement the franc. It's a simple replacement.

src/Franc.elm

module Franc exposing (Franc(..), times)


type alias Amount =
    Int


type Franc
    = Franc Amount


times : Int -> Franc -> Franc
times multiplier (Franc amount) =
    Franc <| multiplier * amount


amount : Franc -> Amount
amount (Franc amount) =
    amount

Since the test passed successfully, the TODO will be as follows.

-[] \ $ 5 + 10 CHF = \ $ 10 (when the rate is 2: 1)

Chapter 6

The books in this chapter use inheritance to eliminate duplicates between Dollar and Franc. There is no concept of direct inheritance in elm, but I think it was an interesting result personally as to how to express it and what kind of effect it has.

To more accurately port the Money class of a book, I think the definition is as follows.

type Dollar = Dollar Amount
type Franc = Franc Amount
type Money = Dolalr | Franc

However, when reproducing the inheritance of OOP, I don't think that elm will bother to write the above code, so I wrote it in the form of * Union Types * as follows. In Java, I think that each class is divided and each definition is described. However, in this case, as you can see from the `times``` and ```amount``` functions, it accepts and returns arguments as the Moneytype, and patterns. By branching with a match, you will describe the processing for each branched type (to be exact, the value). If you are familiar with Java, you may feel the ** unpleasant sign ** ofinstance of` `` and branching by casting. But rest assured. Depending on the compiler, branch types other than Money type will naturally throw an error, and since amount can also be obtained at the time of pattern matching, there will be no cast (and the accompanying runtime error). ..

src/Money.elm

type alias Amount =
    Int


type Money
    = Dollar Amount
    | Franc Amount


times : Int -> Money -> Money
times multiplier money =
    case money of
        Dollar amount ->
            Dollar <| multiplier * amount

        Franc amount ->
            Franc <| multiplier * amount


amount : Money -> Amount
amount money =
    case money of
        Dollar amount ->
            amount

        Franc amount ->
            amount

What I found the power of Elm this time is the type `Int-> Money-> Money``` of times```. If implemented in Java, the signature should look like this: In other words, it is defined in each class and returned as each concrete type. That is, you cannot eliminate duplicates of `` timesunless you move theamount field to type `` Money and return a value as type `` Money```. It is. This implementation will be the story of Chapter 10. Currently, due to the type restrictions of Dollar and Franc, the duplication of times is not completely eliminated, but it is amazing power that the implementation close to Chapter 10 has already been completed in addition to the implementation of Chapter 6. I feel.

// Dollar.java
Dollar times(int multiplier);

// Franc.java
Franc times(int multiplier);

Continuing from the last time, the equivalent comparison is powerful. The Java implementation of equals in the book looks like this: I forcibly cast it to the `Money``` type and compare the amounts. Naturally, the comparison between Dollar and Franc will be successful. Also, although not essential, if you don't compare the instance types closely, you'll get a run-time error with a cast failure when a type other than the Money type comes in. With the `type Money = Dollar Amount | Franc Amount``` notation, the comparison between Dollar and Franc will never succeed, and another type will result in a compilation error as soon as you pass it to the comparison operator. I'm very happy to be protected by the mold.

// Money.java
public boolean equals(Object object) {
    Money money = (Money) object;
    return amount == money.amount;
}

Finally, the test case changes like this: You only need to load the `Money` module, and you can see that `` `times``` is generalized.

module Tests exposing (..)

import Test exposing (..)
import TestExp exposing (..)


-- Test target modules

import Money exposing (..)


all : Test
all =
    describe "Money Test"
        [ describe "Dollar"
            [ "Multiplication1"
                => (Dollar 5 |> times 2)
                === Dollar 10
            , "Multiplication2"
                => (Dollar 5 |> times 3)
                === Dollar 15
            ]
        , describe "Franc"
            [ "Multiplication1"
                => (Franc 5 |> times 2)
                === Franc 10
            , "Multiplication2"
                => (Franc 5 |> times 3)
                === Franc 15
            ]
        ]

The TODO list this time is as follows. The more content you have, the more functional power will appear (all items with strikethroughs). In other words, ** the language automatically supplements the tests to confirm safety and the behavior that you want it to be intuitively **.

-[] \ $ 5 + 10 CHF = \ $ 10 (when the rate is 2: 1)

Whole final code

Chapter 7

I'm sorry. It is additional information. The comparison between US dollars and francs has already ended as of Chapter 6.

Summary

As mentioned at the end of Chapter 6, functional types are characterized by having more restrictions than OOP and procedural types such as Java. But that constraint is by no means a constraint to deprive you of your freedom. By having the compiler check the type carefully, not the cast, the level of abstraction is raised and branching can proceed safely. Plus, you don't have to write verbose code or test code for it. This also helps you focus on your logic code and improve the quality of your application. If you haven't bought the book "Test Driven Development" yet, please buy it and compare it with Java. You will surely notice the charm of Elm!

Recommended Posts

Test Driven Development in Functional Language Elm (Chapter 15-16)
Test Driven Development in Functional Language Elm (Chapter 5-7)
Test-driven development in the functional language Elm
Ruby Study Memo (Test Driven Development)
[Ruby on Rails Tutorial] Error in the test in Chapter 3