Closed7

Elmで学ぶ関数型言語

ぱんだぱんだ

以前、JavaScriptで学ぶ関数型プログラミングを読み、Kotlin x Arrowで関数型プログラミングの触りを学んだ。しかし、それが関数型としての書き方なのかが判断つかず困ってたところScalaのような関数型でもそうでない書き方でも両方できるような言語だと関数型の書き方かどうかがわからない。そのためHaskellのような純粋関数型言語で学んだ方がいい。というアドバイスをもらった。そして、それにはElmがおすすめと言われたのでElm入門してみる。

ぱんだぱんだ

準備

https://guide.elm-lang.jp/install/elm.html

ここからインストーラーでElm本体をインストール。Nodeもなければインストールする。

elm --version
0.19.1

elmの最新バージョンは執筆時点で0.19.1

VSCodeで開発したいので拡張機能もインストールする。

https://github.com/elm-tooling/elm-language-client-vscode

非推奨になってる拡張もあるので注意。elm-toolingの方。

https://marketplace.visualstudio.com/items?itemName=Elmtooling.elm-ls-vscode

プロジェクト初期化を以下でする。

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配下に公式のサンプルコードをコピペ。

Main.elm
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
ぱんだぱんだ

基礎文法

とりあえず書いてみる。以下のようなファイルを準備

Basic.elm
-- モジュール宣言 通常ファイル名がモジュール名となる。 以下の場合は全てを公開。かっこの中身に
-- 公開したい関数や型を指定することで公開単位を指定できる。


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 reactorBasic.elmを表示して確認していく。

ぱんだぱんだ

環境構築

  • elm init まっさらなelmプロジェクト
  • create-elm-app 中身はwebpackでいい感じのテンプレート生成。わかんないけどやったけど動かなかった。
  • vite 公式のテンプレートはないのでコミュニティテンプレートを使うか自力で頑張る。

https://zenn.dev/ababup1192/articles/a51c8e2ddcde77

  • elm-spa SPAプロジェクト用のライブラリ。
  • elm-pages 静的サイト。Gatsbyとかの用途が近い。

https://zenn.dev/y047aka/articles/install-elm-2021

特にこだわりはないのだけどCSSはいい感じに書きたい。コンポーネントというかElmだとモジュール単位でCSSも閉じられているといいと思ってる。

なのでelm-cssか一応慣れてるtailwindを使いたい。

https://zenn.dev/ababup1192/articles/b7516e1846a661

https://zenn.dev/g4yamanaka/articles/50ccec23caf176

結局、bunx create-vite app --template vanila-tsで雛形作ってtailwindを入れて、elm initでelm.json作った。bunを使ったことに特に意味はない。上記zennの記事通り進めるとローカルwebサーバーを起動するときにelm-pluginがうまく読み込めていないようなエラーが出て詰まった。結局公開されていたpackage.json丸コピして起動したらできたのでパッケージのバージョン問題かもしれない。(ここら辺で詰まるとモチベが下がって良くない。)

とりあえず、tailwindをelm内でも使える(class定義するだけだけど)雛形できたからよしとする。

あとたぶんもっと型の恩恵を受けつつtaiwindを使えるモジュールが存在しそうだけど本題とそれそうなのであんまり見ていない。

https://matheus23.github.io/elm-tailwind-modules/

ぱんだぱんだ

成果物

いつものガチャシミュレーションアプリ作った。

https://github.com/JY8752/elm-demo-app

関数型の制約の強さというか型安全というものが何なのかなんとなく感じれた。

bunx create-vite --template vanilla-ts demo-app
elm init
bun i -d vite-plugin-elm
vim vite.config.ts
vite.config.ts
import { defineConfig } from 'vite'
import elmPlugin from 'vite-plugin-elm'

export default defineConfig({
  plugins: [elmPlugin()]
})
Main.elm
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 "+" ]
        ]

コンパイルエラーがでるので以下の型定義を追加。

Main.elm.d.ts
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対応されてないということか?

このスクラップは5ヶ月前にクローズされました