Closed94

Elm guideやりながらメモ

関数型プログラミングの考え方を学びたい。関数型プログラミングの考え方は純粋関数型言語で学ぶべきらしい。HaskellかElmで悩んだが、まずは敷居が低そうな(?)Elmから。まずElm guideを読むとよいらしいので、読みながら気になったことなどをこのスクラップにまとめる

章ごとにスレッド(?)作っていく。

はじめに

ElmはJavaScriptにコンパイルできる関数型言語

サンプルプログラム

  • 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がでにくいとどこかで見た気がする

言語の基礎

動くcliっぽいもの(?)が埋め込まれてる

文字列は"で囲む。結合は++

"butter" ++ "fly"

丁寧に怒ってくれる

> "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

int→float : toFloat
float→int : round, floor, ceiling, truncate

not equalが /=なの、≠みたいでよき

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

boolean、and, or, notがあって、xorもある!使うことあるかな

↑で文字列の結合は++と書いたが、文字列だけじゃなくて配列の結合にも使えるらしい
++ : 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なの?
気になる

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

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の関数を覚えなければ、、
  • 何番目に何を受け取って、みたいなのが読みにくい、、名前つけたい、慣れなのか?

(String -> msg) -> Html msg
ここがスッと入ってこない

整理

  • 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!" ]

なんか違う気がするな

2つエラーがある場合は2つメッセージを表示したほうがいいな

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になる

パターンマッチ

case、OK
ワイルドカード、OK

エラーハンドリング

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を返します。
↑ん?

飽きたのでいったんやめ

このスクラップは17日前にクローズされました
ログインするとコメントできます