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.
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.
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.
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)
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 ** of
instance 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). ..
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 the
amount 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)
I'm sorry. It is additional information. The comparison between US dollars and francs has already ended as of Chapter 6.
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