Closed94

Elm guideやりながらメモ

ゲントクゲントク

はじめに

ゲントクゲントク

サンプルプログラム

  • main, model, update, viewがある
    • mainにinit(modelとその初期値?)update, viewを食わせてる
      • Modelっていう名前のIntのtype aliasと、Msgっていう名前の型?
        • reduxっぽい
    • updateはなにやってるかよくわからない
      • Msg -> Model -> Model ??
      • ↑でreduxっぽいと書いたけど、それでいくとこれはmodelにmsg与えてmodelが返ってくるみたいな意味なのか?
      • インターフェイスと実装?
    • viewもよくわからない
      • divとかbuttonとかをいきなり書けるのか〜
      • Model -> Html Msg わからない
      • 括弧とかカンマの位置、そこなのか
        • この書き方だとdiffがでにくいとどこかで見た気がする
ゲントクゲントク

言語の基礎

ゲントクゲントク

丁寧に怒ってくれる

> "a" + "i"
-- TYPE MISMATCH --------------------------------------------------------- /repl

I cannot do addition with String values like this one:

3|   "a" + "i"
     ^^^
The (+) operator only works with Int and Float values.

Hint: Switch to the (++) operator to append strings!

> 'a' ++ 'i'
-- TYPE MISMATCH --------------------------------------------------------- /repl

The (++) operator cannot append this type of value:

3|   'a' ++ 'i'
     ^^^
I am seeing a character of type:

    Char

But the (++) operator is only for appending List and String values. Maybe put
this value in [] to make it a list?

Hint: I only know how to append strings and lists.
ゲントクゲントク

+演算子
関数なの?なんかnumber -> number -> numberとある。

Add two numbers. The number type variable means this operation can be specialized to Int -> Int -> Int or to Float -> Float -> Float. So you can do things like this:

3002 + 4004 == 7006  -- all ints
3.14 + 3.14 == 6.28  -- all floats
ゲントクゲントク

除算はFloatかIntかで使う演算子が違う
(/) : Float -> Float -> Float
(//) : Int -> Int -> Int

ゲントクゲントク

compare: comparable -> comparable -> Order
Order型というLT | EQ | GTを返す型があるらしい!よさそう

ゲントクゲントク

↑で文字列の結合は++と書いたが、文字列だけじゃなくて配列の結合にも使えるらしい
++ : appendable -> appendable -> appendable
appendable…

ゲントクゲントク

いわゆる「ファンシー」なのかはよくわからないが、数学的にあったらうれしそうなものがある
剰余演算

List.map (modBy 4) [ -5, -4, -3, -2, -1,  0,  1,  2,  3,  4,  5 ]
--                 [  3,  0,  1,  2,  3,  0,  1,  2,  3,  0,  1 ]
ゲントクゲントク

関数は

func param = 処理

の形で定義できる
括弧の使い方がjsと違う

// JavaScript
madlib("cat", "ergonomic")
madlib("butter" + "fly", "metallic")
-- Elm
madlib "cat" "ergonomic"
madlib ("butter" ++ "fly") "metallic"

elmは最小限の括弧にしてるらしい
普通に読みにくいけど、慣れたら「本当にきれいで最小限なものに感じられ」るらしい

ゲントクゲントク

if式(if文じゃないんだ)

if name == "Abraham Lincoln" then
  "Greetings Mr.President!"
else
  "Hey!"

if文じゃないので、returnしなくても値が返る(jsの三項演算子みたい)

ゲントクゲントク

リスト

  • jsでいう配列
  • すべて同じ型を持っていなければいけない
  • Listモジュール
names = ["Alice", "Bob", "Chuck"]

List.isEmpty names
-- False

List.length names
-- 3

List.reverse names
-- ["Chuck", "Bob", "Alice"]

numbers = [4, 3, 2, 1]

List.sort numbers
-- [1, 2, 3, 4]

increment n = n + 1
List.map increment numbers
-- [5, 4, 3, 2]

最後の例、やってることはわかるけどまだ頭でスッと処理できない

ゲントクゲントク

タプル
型が違ってもいいリスト

isGoodName name =
  if String.length name <= 20 then
    (True, "name accepted")
  else
    (False, "name was too long; please limit it to 20 characters")
ゲントクゲントク

レコード
それぞれの値に名前がつけられるタプル

john =
  { first = "John"
  , last = "Hobson"
  , age = 81
  }

john.last
-- "Hobson"
ゲントクゲントク
.last john
-- "Hobson"

List.map .last [john, john, john]
-- ["Hobson", "Hobson", "Hobson"]

更新するとき

{ john | last = "Adams" }
-- { age = 81, first = "John", last = "Adams" }

{ john | age = 22 }
-- { age = 22, first = "John", last = "Hobson" }

既存のレコードが上書きされることはない

ゲントクゲントク
celebrateBirthday person =
 { person | age = person.age + 1 }

celebrateBirthday john
-- { age = 82, first = "John", last = "Hobson" }
ゲントクゲントク

john、別にperson型みたいなかんじで名前つけたりしてないし、celebrateBirthdayはageを持つ型ならなんでもOKなの?
気になる

miyamomiyamo

最小限の型付けをするとこうです

celebrateBirthday : { a | age : number } -> { a | age : number }
celebrateBirthday person =
 { person | age = person.age + 1 }

拡張可能レコード、extensible recordとか言われるやつです
numberは↓のページの制約付き型変数です
https://guide.elm-lang.jp/types/reading_types.html

ゲントクゲントク
ゲントクゲントク

実のところReduxのようなプロジェクトはThe Elm Architecture に着想を得て作られたもの

ゲントクゲントク
main =
  Browser.sandbox { init = init, update = update, view = view }

init, update, viewのレコードをBrowser.sandboxに入れてる?

ゲントクゲントク

View

view : Model -> Html Msg

このviewという関数はModelを引数として受け取り、HTMLを出力します。

Msgがわからない

ゲントクゲントク

各ボタンにonClickハンドラーがあることに注目してください。クリックするとメッセージを生成するということです。つまりこのプラスボタンはIncrementメッセージを生成しています。メッセージを生成するとはどういうことでしょうか、そして生成されたメッセージはどこに行くのでしょうか?update関数に行きます!

ゲントクゲントク

viewで画面が表示される
→ユーザーの操作などによってメッセージが生成される
→updateがメッセージを受け取ってmodelを更新するなどする
→viewが変化したmodelをもとに新しい画面を表示する

ゲントクゲントク

->が何なのかよくわかってないままやってるけどこのまま進むのか…?

ゲントクゲントク

テキストフィールドの演習もやった
updateによってmodelが変化して、それにviewがついていって、みたいなのがなんとなく感覚つかめてきたけどスラスラは書けない

ゲントクゲントク
view : Model -> Html Msg
view model =
  div []
    [ input [ placeholder "Text to reverse", value model.content, onInput Change ] []
    , div [] [ text (String.reverse model.content) ]
    , div [] [ text (String.fromInt (String.length model.content)) ]
    ]

↑こんなかんじであっとるんかな?括弧が二重になってしまっとるが…

ゲントクゲントク

こう?

view : Model -> Html Msg
view model =
  div []
    [ input [ placeholder "Text to reverse", value model.content, onInput Change ] []
    , div [] [ text (String.reverse model.content) ]
    , div [] [ text (model.content |> String.length |> String.fromInt) ]
    ]
ゲントクゲントク

The Elm Architectureのボタン、テキストフィールドをやったが、文法的によくわからないところが残っているので、

Note: このプログラムで Change の値がどんなはたらきをしているのか知りたいなら、カスタム型やパターンマッチの節を見てみてください。

に従って寄り道する

ゲントクゲントク

カスタム型について

type UserStatus = Regular | Visitor
  • UserStatus型は2つのバリアントを持っている
  • ユーザーはRegularVisitorになれる
ゲントクゲントク
type UserStatus
  = Regular
  | Visitor

type alias User =
  { status : UserStatus
  , name : String
  }

thomas = { status = Regular, name = "Thomas" }
kate95 = { status = Visitor, name = "kate95" }
ゲントクゲントク

省略して以下のように書くこともできる

type User
  = Regular String
  | Visitor String

thomas = Regular "Thomas"
kate95 = Visitor "kate95"
ゲントクゲントク

パターンマッチ
Userのnameを返す関数toName

-- 「Userを受け取ってStringを返す方である」と型注釈をつける
toName: User -> String
-- 実装
toName user =
  case user of
    -- UserはRegular String Int | Visitor Stringと定義されているので、そのパターンを書く
    Regular name age ->
      name

    Visitor name ->
      name
ゲントクゲントク

ageは使っていないため、ワイルドカードを使ってRegular name _とするのが一般的

ゲントクゲントク

カスタム型、パターンマッチの寄り道終わり
というか、次の章を先読みする形になっていたのか

ゲントクゲントク

The Elm Architecture > フォーム

いつものように、モデルを考えることからはじめます。

サ〜っと読んでいたから今気づいたけど、Elmでなにか作るときは(?)Model→Update→Viewの順で考えていくのがいいのか

ゲントクゲントク

Modelを考える。「名前、パスワード、パスワード(確認用)のフィールドを持ったフォーム」を作るので、それぞれの文字列の状態をもつ必要がありそう→

type alias Model =
  { name: String
  , password: String
  , passwordAgain: String
  }

この、カンマが最初に来るようなフォーマット名前あるのかな?

ゲントクゲントク

Updateを考える。Updateはメッセージ(Msg)を受け取ってモデル(Model -> Model)を返す

update: Msg -> Model -> Model

Modelで持つ3つの状態をそれぞれ変更させるようなMsgを考える。

type Msg
  = Name String
  | Password String
  | PasswordAgain String
ゲントクゲントク

Msgみたいなカスタム型の定義のしかた、ソラで書ける気がしないのでちょっとまとめてみる

  • type MsgでMsgという名前をつける
  • |を使って、Msgは3つのバリアントNamePasswordPasswordAgainを持っていることを表す
  • Name Stringのように書くことで、関連するデータをもたせることができる

https://guide.elm-lang.jp/types/custom_types.html

ゲントクゲントク

Updateに戻る。
updateの型は考えたので、実装

-- msgとmodelを受け取る(actionとstateを受け取るreducerっぽい)
update msg model =
  case msg of
    -- String型の「関連するデータ」にnameと名前をつけている
    Name name ->
      -- modelの中のnameだけ替えたものを返す
      { model | name =name }
    Password password ->
      { model | password = password }
    PasswordAgain password ->
      { model | passwordAgain = password }
ゲントクゲントク

View
elmはinputとかdivがただの関数。なので、補助関数を自分で作ることもできる。例では、viewInputviewValidationを作っている

viewInput

viewInput : String -> String -> String -> (String -> msg) -> Html msg
viewInput t p v toMsg =
  input [ type_ t, placeholder p, value v, onInput toMsg ] []

inputはattributeとhtmlを受け取る関数だけど、htmlは受け取らないので、[]にしてしまっている?

  • htmlの関数を覚えなければ、、
  • 何番目に何を受け取って、みたいなのが読みにくい、、名前つけたい、慣れなのか?
ゲントクゲントク

整理

  • viewInputの実装では、onInputとしてtoMsgを受け取っている
  • viewの実装では、NamePasswordなどのMsgを渡している
  • type Msgで定義してあるとおり、NameはStringを受け取る関数
ゲントクゲントク

あ、toMsg(String -> msg)
それで、msgを受け取ってHtmlを返すinputというわけ?

ゲントクゲントク

viewValidationModelを受け取ってHtml msgを返す関数
model内の2つのpasswordを比較して、一致不一致のif式でdivを返す

viewValidation : Model -> Html msg
viewValidation model =
  if model.password == model.passwordAgain then
    div [ style "color" "green" ] [ text "OK" ]
  else
    div [ style "color" "red"] [ text "Password do not match!" ]

styleはcssのプロパティと値を設定する関数っぽい

ゲントクゲントク

パスワードが8文字より長いか確認する。

viewValidation : Model -> Html msg
viewValidation model =
  if model.password == model.passwordAgain then
    if String.length(model.password) > 8 then
      div [ style "color" "green" ] [ text "OK" ]
    else
      div [ style "color" "red" ] [ text "Passwords must be more than 8 characters!" ]
  else
    div [ style "color" "red" ] [ text "Passwords do not match!" ]

なんか違う気がするな

ゲントクゲントク
viewValidation : Model -> Html msg
viewValidation model =
  div []
    [ if not ( isSameString model )
      then div [ style "color" "red" ] [ text "Passwords do not match!" ]
      else div [] []
    , if not ( isStringMoreThanEight model )
      then div [ style "color" "red" ] [ text "Passwords must be more than 8 characters!" ]
      else div [] []
    , if isSameString model && isStringMoreThanEight model
      then div [ style "color" "green" ] [ text "OK" ]
      else div [] []
    ]

isSameString : Model -> Bool
isSameString model =
  model.password == model.passwordAgain

isStringMoreThanEight : Model -> Bool
isStringMoreThanEight model =
  String.length(model.password) > 8

う〜ん、これじゃない感

  • 「条件に当てはまらなかった場合は表示しない」のやりかたがわからなかった(htmlを返すと型で決めてるので、空のdivでも返さないといけない)
  • エラーをモデルでもって、mapして表示するとかにしたほうがいいのかな?
ゲントクゲントク

あとパスワードが大文字小文字数字含むか確認する、というのは、ブラウザのエディタだとimport Regexってできなかったのでやめた

ゲントクゲントク


(1文字ではスクラップ作れないみたい)

ゲントクゲントク

いうてここはThe Elm Architectureやりながらチラチラ見てたので、初見ではない

ゲントクゲントク

関数、OK
部分適用のはなしもOK
部分適用、パイプラインは過度に使うと良くないらしい

型注釈、OK

ゲントクゲントク

型変数
ジェネリクス?
小文字で書く必要がある

。型変数のaやbといった1文字のものは慣例によりいたるところで使われていますが、より具体的な名前を付けたほうがいい場合もあります。

え、どのような場合であっても具体的な名前つけたい

ゲントクゲントク

制約付き方変数

  • number … Int, Float
  • appendable … String, List a
  • comparable … Int, Float, Char, String, comparableのList, comparableのタプル
  • compappend … String, List comparable

+<を柔軟に使えるようにするために存在している

ゲントクゲントク

型エイリアス

モデル、OK

レコードコンストラクター
そういう名前だったのか
レコードに限った話、レコード以外の方に対して型エイリアスを作っても、コンストラクターは作成されない

ゲントクゲントク

カスタム型

ユニオン型と呼ばれていた。他のコミュニティではtagged unionとかADT(代数的データ型)などと呼ばれているらしい

ゲントクゲントク
type User
  = Regular String Int
  | Visitor String

のように定義すると、RegularString -> Int -> UserVisitorString -> Userになる

ゲントクゲントク

エラーハンドリング

ゲントクゲントク

Elmはエラーをデータとして扱う

type MaybeAge
  = Age Int
  | InvalidInput

toAge : String -> MayAge

どんなエラーかによって型をわけたり

type MaybePost
  = Post { title: String, content: String }
  | NotTitle
  | NoContent

toPost : String -> String -> MaybePost
ゲントクゲントク

Maybeの使いすぎを避ける

type alias Friend =
  { name : String
  , hieght : Maybe Float
  , weight : Maybe Float
  }

これよりも

type Friend
  = Less String
  | More String Info

type alias Info =
  { age : Int
  , height : Float
  , weight : Float
  }

こっちのほうがいい
状態がLessとMoreの2つしかないから

ゲントクゲントク

フォームの例でtype alias Modelをtype Modelにしてrebuildしたら次のように怒られた

I am partway through parsing a custom type, but I got stuck here:

25| type Model =
26|   { name : String
      ^
I was expecting to see a variant name next. Something like Success or Sandwich.
Any name that starts with a capital letter really!

Note: Here is an example of a valid `type` declaration for reference:

    type Status
      = Failure
      | Waiting
      | Success String

This defines a new `Status` type with three variants. This could be useful if we
are waiting for an HTTP request. Maybe we start with `Waiting` and then switch
to `Failure` or `Success "message from server"` depending on how things go.
Notice that the Success variant has some associated data, allowing us to store a
String if the request goes well!
ゲントクゲントク

なんとなくわかったきがする
↑のFriendとInfoの例だと、{ age : Int, height: Float, weight: Float }はそれ自体が型を表現しているので、type Info =とするのは誤りで、あくまで「別名をつける」といういみで、type asias Info =としないといけない

ゲントクゲントク

Result
年齢を入力するサイトの例。有効な年齢かどうかを判定する関数

isReasonableAge : String -> Result String Int
isReasonableAge input =
  case String.toInt input of
    Nothing ->
      Err "That is not a number"

    Just age ->
      if age < 0 then
        Err "Please try again after you are born."

      else if age > 135 then
        Err "Are you some kind of turtle?"

      else
        Ok age

いまだにString -> Result String Intがよくわからない、、、

ゲントクゲントク

Resultの型が

type Result error value
  = Ok value
  | Err error

だから、String -> Result String Intなんだ

ゲントクゲントク

関数の中ではcase式でErrを返すかOkを返すか決めてる
String.toIntString -> Maybe Intなので、NothingJust Intを返す
Nothingだった場合、Err
Just ageだった場合、ageをif式で判定して、問題あればErr、なければOk age

ゲントクゲントク

わかったらスルスルっと入ってきて気持ちいいが、まだ書けないな

ゲントクゲントク

コマンドとサブスクリプション

ゲントクゲントク

Browser.sandbox←いままで使ってたやつ
Browser.element←これから使うやつ

Browser.elementには、外の世界とやり取りするためのコマンドとサブスクリプションの概念がある

ゲントクゲントク

init

init : () -> (Model, Cmd Msg)
init _ =
  ( Loading
  , Http.get
    { url = "https://elm-lang.org/assets/public-opinion.txt"
    , expect = Http.expectString GotText
    }
  )

Modelの初期値と実行したいコマンドを返している

ここで返しているコマンドは最終的にはupdate関数に渡されるMsgを返します。
↑ん?

このスクラップは2021/11/15にクローズされました