3
\$\begingroup\$

I am an elm beginner and I want to provide an elm practical course to my coworkers.

I will do a short presentation of elm, then we will move to practical lessons.

The first lesson is to read together the following file to discover elm basis and syntax:

Basis.elm

{-
Here we will discover some elm basis and syntax.

You can hack this file and run the tests by running in your terminal:
elm reactor

And go to http://localhost:8000/
Then select Basis.elm file
______________________________________________________________________

First of all, we are going to import some testing packages.

We import the Expect package so we can use Expect package functions.
For example we will be able to call Expect.equal to check equality.
-}

import Expect

{-
Then we import Test.Runner.Html and expose TestProgram to our program.
Since we exposed TestProgram we can call this function without prefixing it with the package name.
-}

import Test.Runner.Html exposing (TestProgram)

-- Finally we import Test and expose every functions of the package
import Test exposing (..)


{-
Let's talk about functions as it's a key component of elm.

This is a function:
-}

add x y = x + y
{-  ^ ^   ^^^^^
    | |     |
    | |     |--- there is no return keyword, everything is a returning expression
    | |
    |-|--------- first and second arguments

To call a function, you write:
myResult = myFunction myFirstArgument mySecondArgument
-}

result = add 4 5

{-
To write an anonymous function we use the following syntax
(\myFirstArgument mySecondArgument -> myFirstArgument * mySecondArgument)

so we can probably write something like that:
-}

resultComputedByAnonymousFunction = (\a b -> a + b) 4 5

{-
The signature of our add function is:
number -> number -> number

If translated in Javascript, our function looks like:
function add (x) {
    return function(y) {
        return x + y;
    }
}

It's a function taking one number (x) returning
a function expecting another number (y) returning
a number which is the result of the operation x + y.

Functions are curried,
so we can create a new function from our add function:
-}

add3 = add 3

seven = add3 4

{-
We are going to unit test our previously written adding function.

The signature of the test function is:
String -> (() -> Expectation) -> Test

It takes a String and a function returning an Expectation and finally returns a Test.

Let's test the add function.
-}

testAddFunction = test "add function should addition the arguments" (\_ -> Expect.equal 4.12 (add 2 2.12))


-- Now let's write a dividing function
divide x y =
    x / y

{-
We want a function that divide by 2, so we need to flip the arguments of the function.
So we can use a core function for that purpose:
    http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Basics#flip
-}

divideBy2 = flip divide 2

-- let's compute ( 4 + 6 ) / 2
mean = divideBy2 (add 4 6)


testFunctionComposition = test "composition of divideBy2 and add functions should return the mean value" (\_ -> Expect.equal 5 mean)

{-
Finally let's find a more natural way to express what we are doing here.

We can use backward/forward function application:
http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Basics#|>
-}

meanByPipe = add 4 6 |> divideBy2

testPipe = test "composition and pipe should return the same value" <|
    \_ -> Expect.equal mean meanByPipe


{-
Strings are always double-quoted.
Single quote is used for Char.

The operator to append a String is ++.
-}

-- So this is a Char
myChar = 'l'

-- and those are Strings
myString = "abc"
mySecondString = "def"

testAppendOperator = test "append operator should append strings"  <|
    \_ -> Expect.equal "abcdef" (myString ++ mySecondString)

{-
A record is a kind of logic-less object.

This is a record:
-}

johnDoe =
    { firstName = "John"
    , lastName = "Doe"
    }

-- We can access a record field with the dot notation
testRecordAccessor = test "accessor .firstName should return the firstName of a record" <|
    \_ -> Expect.equal "John" johnDoe.firstName

-- To get an updated copy of a record we use the pipe operator
lioDoe = { johnDoe | firstName = "Lio" }

testUpdateRecord = test "Pipe operator should 'update' the firstName of the record" <|
    \_ -> Expect.equal "Lio" lioDoe.firstName

-- With the pipe operator we can change multiple fields at once
lioDa =
    { johnDoe |
        firstName = "Lio",
        lastName = "Da"
    }

testUpdateMultipleFieldsRecord =
    test "Pipe operator should 'update' multi fields" <|
        \_ -> Expect.equal {firstName = "Lio", lastName = "Da"} lioDa

{-
A record is a type, so johnDoe, lioDoe and lioDa are typed { firstName: String, lastName: String }.
We can clarify typing with type annotation.
-}

janeDoe : { firstName: String, lastName: String }
janeDoe = { firstName = "Jane", lastName = "Doe" }


{-
And to simplify type usage we can alias a type.
Let's define an alias to our type { firstName: String, lastName: String }.
-}

type alias Identity =
    { firstName : String
    , lastName : String
    }

saraDoe : Identity
saraDoe = { firstName = "Sara", lastName = "Doe" }

-- Identity is also a record constructor
claireDoe : Identity
claireDoe = Identity "Claire" "Doe"

testRecordConstructor = test "Record constructor should return an record" <|
    \_ -> Expect.equal "Claire" claireDoe.firstName

{-
Since we discovered some basic types, let's move to union type.

Union type is a flexible feature used for many things in elm.

For example, Bool is the union type of True and False.

Bool = True | False
 ^      ^       ^
 |      |       |
 |      |-------|--- constructors
 |
 |------------------ the union type


With union types we can naturally express things such as an answer to a survey:
-}

type Answer = Yes | No | Other String

yes : Answer
yes = Yes

no : Answer
no = No

iDoNotUnderstand : Answer
iDoNotUnderstand = Other "i don't understand the question"

{-
We can do pattern matching with the case of instruction.
-}

testPatternMatching =
    test "yes should be Yes" (\_ ->
        case yes of
            Yes -> Expect.pass
            No -> Expect.fail "yes is not a No"
            Other response -> Expect.fail "yes is not an Other"
        )

main : Test.Runner.Html.TestProgram
main =
    [ testAddFunction
    , testAppendOperator
    , testPipe
    , testRecordAccessor
    , testUpdateRecord
    , testUpdateMultipleFieldsRecord
    , testRecordConstructor
    , testPatternMatching
    ]
        |> concat
        |> Test.Runner.Html.run

elm-package.json

{
    "version": "1.0.0",
    "summary": "helpful summary of your project, less than 80 characters",
    "repository": "https://github.com/user/project.git",
    "license": "BSD3",
    "source-directories": [
        "."
    ],
    "exposed-modules": [],
    "dependencies": {
        "elm-community/elm-test": "4.1.1 <= v < 5.0.0",
        "elm-community/html-test-runner": "1.0.3 <= v < 2.0.0",
        "elm-lang/core": "5.1.1 <= v < 6.0.0",
        "elm-lang/html": "2.0.0 <= v < 3.0.0"
    },
    "elm-version": "0.18.0 <= v < 0.19.0"
}

Then we will write some unit tests to discover List and Tuple, write a simple application to discover Elm Architecture and finally build a more complex application with Commands.

Could you review the Basis.elm file to improve this lesson?

\$\endgroup\$
2
  • 1
    \$\begingroup\$ I've changed the highlighting language to Haskell, since the prettifier didn't recognize the comment blocks. Unfortunately, there is no lang-elm yet. Feel free to change the highlighting to something else if you think it's more appropriate. \$\endgroup\$
    – Zeta
    Aug 30, 2017 at 12:02
  • 1
    \$\begingroup\$ @Zeta As a moderator, I have added an association between the elm tag and lang-hs, which should probably help with all future Elm questions. \$\endgroup\$ Aug 30, 2017 at 13:51

1 Answer 1

2
\$\begingroup\$

I didn't know there was an Elm language - your posting introduced me to it.

resultComputedByAnonymousFunction = (\a b -> a + b) 4 5

When reading from a whiteboard, one has to be able to pronounce things. Apparently the correct pronunciation here would involve "lambda a b". I found this explanation a little surprising. And I suppose ( \ a b ... would work the same; it is nothing like writing \n for newline. I don't know if your audience is allergic to Greek letters, but you might want to slightly expand the anonymous function section.

meanByPipe = add 4 6 |> divideBy2

This is fine, you are introducing one thing at a time, in this case a new operator. But in the next line you spring <| on us. You might want to define testPipe twice, showing equivalent functions with and without <|.

I recommend phrasing it like this: Single quote is used for a single Char.

For johnDoe / alias / main, I think you believe that comma-at-start-of-line improves readability or minimizes diffs. I don't share your opinion (preferring e.g. lioDa), but that's fine. Python has spoiled me, by allowing trailing comma so all lines are identically formatted.

Before yes = Yes, you might explicitly point out they are two distinct identifiers.

main impresses me as being a bit fragile, making it easy to add testFoo and neglect to include it in the list. If there is some introspection method for obtaining a list of identifiers, I encourage you to use it here.

Overall, your language and expository style are clear, appropriate for a beginning audience.

\$\endgroup\$
6
  • 1
    \$\begingroup\$ Re: comma-at-start-of-line -- FYI this is conventional style in Haskell, which I believe propagated into Elm. \$\endgroup\$
    – BenC
    Sep 9, 2017 at 1:05
  • \$\begingroup\$ @j-h thank you very much for your review! You have been most helpful. I did the lesson yesterday with one group of coworker and most of your points have been a problem during the lesson, especially the backward function application. I have corrected my file based on your remarks and I was wondering if you could review my changes and how? \$\endgroup\$
    – Simbas
    Sep 9, 2017 at 19:24
  • \$\begingroup\$ @BenC it is also the conventional style in Elm elm-lang.org/docs/style-guide \$\endgroup\$
    – Simbas
    Sep 9, 2017 at 19:25
  • \$\begingroup\$ @Simbas Could you revert the changes back to your original source? Posted code shouldn't be edited, because then the context of the review gets lost. If you want a review of your updated code you should post a new question. \$\endgroup\$
    – RoToRa
    Nov 6, 2017 at 8:11
  • \$\begingroup\$ @RoToRa this is the original source, what changes are you talking about? \$\endgroup\$
    – Simbas
    Nov 6, 2017 at 17:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.