エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

Elm の JSON のパースの覚え書き

Elm に手を出しました。

mousemoveイベントハンドラを使うのに MouseEventJSON をパースする必要があって四苦八苦したので、明日の自分のために JSON のパースの仕方をメモっておきます。 後日イベントハンドラについても書く予定。

パーサを書く

SomethingJson という雑なモジュールを作成しました。

module SomethingJson exposing
  ( Something
  , Somethings
  , somethingDecoder
  , parseSomething
  , parseSomethings
  , FooBarBaz
  , parseFooBarBaz
  )

import Json.Decode exposing
  ( Decoder
  , Error
  , decodeString
  , at
  , string
  , int
  , map
  , map3
  , list
  )

-- "something" という文字列の要素を持つレコード型
type alias Something =
  { something : String
  }

-- Something のリスト型
type alias Somethings =
  List Something

-- "something" という要素を含む JSON のデコーダ
somethingDecoder : Decoder String
somethingDecoder =
  at ["something"] string

-- JSON 文字列をパースして Something 型の値を返す関数
parseSomething : String -> Result Error Something
parseSomething =
  decodeString (map Something somethingDecoder)

-- JSON 文字列をパースして Somethings 型の値を返す関数
parseSomethings : String -> Result Error Somethings
parseSomethings =
  decodeString (list (map Something somethingDecoder))

-- 三つの要素を持つレコード型
type alias FooBarBaz =
  { foo : String
  , bar : Int
  , baz : List Int
  }

-- "foo", "bar", "baz" という要素を含む JSON のデコーダ
fooBarBazDecoder : Decoder FooBarBaz
fooBarBazDecoder =
  map3 FooBarBaz
    (at ["foo"] string)
    (at ["bar"] int)
    (at ["baz"] (list int))

-- JSON 文字列をパースして FooBarBaz 型の値を返す関数
parseFooBarBaz : String -> Result Error FooBarBaz
parseFooBarBaz =
  decodeString fooBarBazDecoder

elm init した時に作成される src/ というディレクトリに SomethingJson.elm という名前で保存します。

パーサを使う

REPL で動作を確認します。

$ elm repl
---- Elm 0.19.0 ----------------------------------------------------------------
Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
--------------------------------------------------------------------------------
>

インポートします。

> import SomethingJson

Something 型を表す JSON をパースします。

> SomethingJson.parseSomething "{\"something\":\"何か\"}"
Ok { something = "何か" }
    : Result Json.Decode.Error SomethingJson.Something

Somethings 型(Something のリスト型)を表す JSON をパースします。

> SomethingJson.parseSomethings "[{\"something\":\"何か\"},{\"something\":\"どれか\"}]"
Ok [{ something = "何か" },{ something = "どれか" }]
    : Result Json.Decode.Error SomethingJson.Somethings

三種類の型の値を持つ FooBarBaz 型を表す JSON をパースします。

> SomethingJson.parseFooBarBaz "{\"foo\":\"ふー\",\"bar\":123,\"baz\":[1,2,3]}"
Ok { bar = 123, baz = [1,2,3], foo = "ふー" }
    : Result Json.Decode.Error SomethingJson.FooBarBaz

もっと要素の多い JSON のパーサをかく

Json.Decode には map8 まで用意されていて 8 要素まではパーサを書くことができます。それ以上の場合はドキュメントにも記載されているように他のパッケージなどを利用します。

Note: If you run out of map functions, take a look at elm-json-decode-pipeline which makes it easier to handle large objects, but produces lower quality type errors.

リンクされている elm-json-decode-pipeline を使ってみます。

インストールします。

$ elm install NoRedInk/elm-json-decode-pipeline

HTML の MouseEvent の内容を解釈するパーサを書いてみます。

module MouseEvents exposing (EventData, mouseEventDecoder, parseMouseEvent)

import Json.Decode exposing (Decoder, Error, decodeString, int, bool, succeed)
import Json.Decode.Pipeline exposing (required)

type alias EventData =
  { altKey : Bool
  , ctrlKey : Bool
  , shiftKey : Bool
  , metaKey : Bool
  , button : Int
  , clientX : Int
  , clientY : Int
  , movementX : Int
  , movementY : Int
  , screenX : Int
  , screenY : Int
  }

mouseEventDecoder : Decoder EventData
mouseEventDecoder =
  succeed EventData
    |> required "altKey" bool
    |> required "ctrlKey" bool
    |> required "shiftKey" bool
    |> required "metaKey" bool
    |> required "button" int
    |> required "clientX" int
    |> required "clientY" int
    |> required "movementX" int
    |> required "movementY" int
    |> required "screenX" int
    |> required "screenY" int

parseMouseEvent : String -> Result Error EventData
parseMouseEvent =
  decodeString mouseEventDecoder

MouseEvents.elm というファイル名で src/ に保存します。

REPL で確認します。

$ elm repl
---- Elm 0.19.0 ----------------------------------------------------------------
Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
--------------------------------------------------------------------------------
> import MouseEvents
> MouseEvents.parseMouseEvent "{\"screenX\":1,\"screenY\":1,\"movementX\":1,\"movementY\":1,\"clientX\":1,\"clientY\":1,\"button\":0,\"metaKey\":true,\"shiftKey\":true,\"ctrlKey\":true,\"altKey\":true}"
Ok { altKey = True, button = 0, clientX = 1, clientY = 1, ctrlKey = True, metaKey = True, movementX = 1, movementY = 1, screenX = 1, screenY = 1, shiftKey = True }
    : Result Json.Decode.Error MouseEvents.EventData

これで MouseEvent を扱う準備ができました。つづく。

いつか読むはずっと読まない:はじまりの艦隊

The Lost Fleet (彷徨える艦隊)の最新刊、The Genesis Fleet: Vanguard 、の邦訳、ようやく読了。って刊行から一年近く経ってしまっていた。

来月には原著のシリーズ最新刊 The Genesis Fleet: Triumphant がもう刊行される模様。

彷徨える艦隊 ジェネシス 先駆者たち (ハヤカワ文庫SF)

彷徨える艦隊 ジェネシス 先駆者たち (ハヤカワ文庫SF)