Elm guideやりながらメモ
関数型プログラミングの考え方を学びたい。関数型プログラミングの考え方は純粋関数型言語で学ぶべきらしい。HaskellかElmで悩んだが、まずは敷居が低そうな(?)Elmから。まずElm guideを読むとよいらしいので、読みながら気になったことなどをこのスクラップにまとめる
章ごとにスレッド(?)作っていく。
はじめに
ElmはJavaScriptにコンパイルできる関数型言語
サンプルプログラム
- main, model, update, viewがある
- mainにinit(modelとその初期値?)update, viewを食わせてる
- Modelっていう名前のIntのtype aliasと、Msgっていう名前の型?
- reduxっぽい
- Modelっていう名前のIntのtype aliasと、Msgっていう名前の型?
- updateはなにやってるかよくわからない
- Msg -> Model -> Model ??
- ↑でreduxっぽいと書いたけど、それでいくとこれはmodelにmsg与えてmodelが返ってくるみたいな意味なのか?
- インターフェイスと実装?
- viewもよくわからない
- divとかbuttonとかをいきなり書けるのか〜
- Model -> Html Msg わからない
- 括弧とかカンマの位置、そこなのか
- この書き方だとdiffがでにくいとどこかで見た気がする
- mainにinit(modelとその初期値?)update, viewを食わせてる
言語の基礎
動く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.
Basics
演算子のドキュメント
IntとFloatがある
+
演算子
関数なの?なんか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
Equality, Comparison
not equalが /=なの、≠みたいでよき
compare
: comparable -> comparable -> Order
Order
型というLT | EQ | GT
を返す型があるらしい!よさそう
↑松竹梅を比較してOrderを返す関数の例
boolean、and, or, notがあって、xorもある!使うことあるかな
↑で文字列の結合は++
と書いたが、文字列だけじゃなくて配列の結合にも使えるらしい
++
: appendable -> appendable -> appendable
appendable…
Fancier Math
…?
いわゆる「ファンシー」なのかはよくわからないが、数学的にあったらうれしそうなものがある
剰余演算
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 ]
三角関数の計算のためのいろいろもある
Function Helpersぜんぜんわからん…
後で読む
次回ここから読む
関数は
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は↓のページの制約付き型変数です
ありがとうございます!!
サンプルコードをスクラップのmarkdownエディタに直接書くのきつくなってきたのでvscodeの拡張機能いれた
elmで検索して一番上に出てくるやつはdeprecatedだったので↓をいれた
実のところReduxのようなプロジェクトはThe Elm Architecture に着想を得て作られたもの
main
はElmでは特別な値
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つのバリアントを持っている - ユーザーは
Regular
かVisitor
になれる
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"
矢印の説明はないかもなので
A -> B
「型Aを受け取って型Bを返す関数」の型です
矢印は関数の型を表すキーワード?ですね
あとは↓のページで
パターンマッチ
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つのバリアントName
とPassword
とPasswordAgain
を持っていることを表す -
Name String
のように書くことで、関連するデータをもたせることができる
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がただの関数。なので、補助関数を自分で作ることもできる。例では、viewInput
とviewValidation
を作っている
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の実装では、
Name
やPassword
などのMsg
を渡している -
type Msg
で定義してあるとおり、Name
はStringを受け取る関数
あ、toMsg
は(String -> msg)
か
それで、msg
を受け取ってHtml
を返すinput
というわけ?
viewValidation
はModel
を受け取って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
のように定義すると、Regular
はString -> Int -> User
、Visitor
はString -> 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とtype aliasの違い、、?
フォームの例で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.toInt
はString -> Maybe Int
なので、Nothing
かJust 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を返します。
↑ん?
飽きたのでいったんやめ