Elmで学ぶ関数型言語
以前、JavaScriptで学ぶ関数型プログラミングを読み、Kotlin x Arrowで関数型プログラミングの触りを学んだ。しかし、それが関数型としての書き方なのかが判断つかず困ってたところScalaのような関数型でもそうでない書き方でも両方できるような言語だと関数型の書き方かどうかがわからない。そのためHaskellのような純粋関数型言語で学んだ方がいい。というアドバイスをもらった。そして、それにはElmがおすすめと言われたのでElm入門してみる。
とりあえず公式の日本語訳を読み込む。
準備
ここからインストーラーでElm本体をインストール。Nodeもなければインストールする。
elm --version
0.19.1
elmの最新バージョンは執筆時点で0.19.1
。
VSCodeで開発したいので拡張機能もインストールする。
非推奨になってる拡張もあるので注意。elm-tooling
の方。
プロジェクト初期化を以下でする。
elm init
$ tree .
.
├── elm-stuff
│ └── 0.19.1
│ ├── d.dat
│ ├── i.dat
│ ├── lock
│ └── o.dat
├── elm.json
└── src
elm.json
がプロジェクトの設定情報。
{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/html": "1.0.0"
},
"indirect": {
"elm/json": "1.1.3",
"elm/time": "1.0.0",
"elm/url": "1.0.0",
"elm/virtual-dom": "1.0.3"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}
とりあえず、src
配下に公式のサンプルコードをコピペ。
module Main exposing (..)
-- Press buttons to increment and decrement a counter.
--
-- Read how it works:
-- https://guide.elm-lang.org/architecture/buttons.html
--
import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
-- MAIN
main =
Browser.sandbox { init = init, update = update, view = view }
-- MODEL
type alias Model = Int
init : Model
init =
0
-- UPDATE
type Msg
= Increment
| Decrement
update : Msg -> Model -> Model
update msg model =
case msg of
Increment ->
model + 1
Decrement ->
model - 1
-- VIEW
view : Model -> Html Msg
view model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [] [ text (String.fromInt model) ]
, button [ onClick Increment ] [ text "+" ]
]
以下コマンドでブラウザからプロジェクトファイルの確認、ビルドなどできるよう。
elm reactor
Go to http://localhost:8000 to see your project dashboard.
以下コマンドでelmファイルをコンパイルすることができる。
# HTMLファイルにコンパイル
$ elm make src/Main.elm
Success!
Main ───> index.html
# JSにコンパイル
$ elm make src/Main.elm --optimize --output=elm.js
Success!
Main ───> elm.js
準備続き
テストとフォーマット用に以下をインストール
npm install -g elm-test elm-format
基礎文法
とりあえず書いてみる。以下のようなファイルを準備
-- モジュール宣言 通常ファイル名がモジュール名となる。 以下の場合は全てを公開。かっこの中身に
-- 公開したい関数や型を指定することで公開単位を指定できる。
module Basic exposing (..)
import Html exposing (text)
main : Html.Html msg
main =
text "Hello World!!"
module Basic exposing (..)
これがモジュール宣言。おそらくElmはファイルごとの処理をモジュールとしており、公開する機能を指定できる。モジュール名は通常ファイル名となる。
import Html exposing (text)
これでHtmlモジュールをインポートしてる。いわゆる標準モジュール。
mainが2行になるのはelm-formatで自動でされた。また、型注釈を明示的に書かないと警告出たので書いた。Html.Html
でHtmlモジュールのHtml型ということ。msg
は型パラメーターみたい。webにおけるさまざまなイベントを表すみたい。後で詳しく見ていく。
elm reactor
でBasic.elm
を表示して確認していく。
環境構築
- elm init まっさらなelmプロジェクト
- create-elm-app 中身はwebpackでいい感じのテンプレート生成。わかんないけどやったけど動かなかった。
- vite 公式のテンプレートはないのでコミュニティテンプレートを使うか自力で頑張る。
- elm-spa SPAプロジェクト用のライブラリ。
- elm-pages 静的サイト。Gatsbyとかの用途が近い。
特にこだわりはないのだけどCSSはいい感じに書きたい。コンポーネントというかElmだとモジュール単位でCSSも閉じられているといいと思ってる。
なのでelm-cssか一応慣れてるtailwindを使いたい。
結局、bunx create-vite app --template vanila-ts
で雛形作ってtailwindを入れて、elm init
でelm.json作った。bunを使ったことに特に意味はない。上記zennの記事通り進めるとローカルwebサーバーを起動するときにelm-plugin
がうまく読み込めていないようなエラーが出て詰まった。結局公開されていたpackage.json
丸コピして起動したらできたのでパッケージのバージョン問題かもしれない。(ここら辺で詰まるとモチベが下がって良くない。)
とりあえず、tailwindをelm内でも使える(class定義するだけだけど)雛形できたからよしとする。
あとたぶんもっと型の恩恵を受けつつtaiwindを使えるモジュールが存在しそうだけど本題とそれそうなのであんまり見ていない。
成果物
いつものガチャシミュレーションアプリ作った。
関数型の制約の強さというか型安全というものが何なのかなんとなく感じれた。
bunx create-vite --template vanilla-ts demo-app
elm init
bun i -d vite-plugin-elm
vim vite.config.ts
import { defineConfig } from 'vite'
import elmPlugin from 'vite-plugin-elm'
export default defineConfig({
plugins: [elmPlugin()]
})
module Main exposing (Model, Msg(..), init, main, update, view)
import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
-- MAIN
main =
Browser.sandbox { init = init, update = update, view = view }
-- MODEL
type alias Model =
Int
init : Model
init =
0
-- UPDATE
type Msg
= Increment
| Decrement
update : Msg -> Model -> Model
update msg model =
case msg of
Increment ->
model + 1
Decrement ->
model - 1
-- VIEW
view : Model -> Html Msg
view model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [] [ text (String.fromInt model) ]
, button [ onClick Increment ] [ text "+" ]
]
コンパイルエラーがでるので以下の型定義を追加。
export var Elm: any;
ここまでやって起動しようとしると以下のエラーが出る。
bun dev
$ vite
failed to load config from /Users/yamanakajunichi/work/myapp/study/elm-demo-app/test/vite.config.ts
error when starting dev server:
TypeError: elmPlugin is not a function
at file:///Users/yamanakajunichi/work/myapp/study/elm-demo-app/test/vite.config.ts.timestamp-1701869514630-ea77e519a966.mjs:5:13
at ModuleJob.run (node:internal/modules/esm/module_job:217:25)
at async ModuleLoader.import (node:internal/modules/esm/loader:308:24)
at async loadConfigFromBundledFile (file:///Users/yamanakajunichi/work/myapp/study/elm-demo-app/test/node_modules/vite/dist/node/chunks/dep-YJaePtkC.js:67585:21)
at async loadConfigFromFile (file:///Users/yamanakajunichi/work/myapp/study/elm-demo-app/test/node_modules/vite/dist/node/chunks/dep-YJaePtkC.js:67442:28)
at async resolveConfig (file:///Users/yamanakajunichi/work/myapp/study/elm-demo-app/test/node_modules/vite/dist/node/chunks/dep-YJaePtkC.js:67046:28)
at async _createServer (file:///Users/yamanakajunichi/work/myapp/study/elm-demo-app/test/node_modules/vite/dist/node/chunks/dep-YJaePtkC.js:59596:20)
at async CAC.<anonymous> (file:///Users/yamanakajunichi/work/myapp/study/elm-demo-app/test/node_modules/vite/dist/node/cli.js:764:24)
error: script "dev" exited with code 1 (SIGHUP)
package.jsonのtype: module
を削除したら動いた。vite-pluginがESM対応されてないということか?