uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかReactとかFregeとかJavaとか -

G*なWeb API

Grails3もカウントダウンな感じのおり、イベント紹介です。以下引用。

G*ワークショップZは日本Grails/Groovyユーザーグループの定例イベントです。Java仮想マシン上で動作するGroovyGrailsGradleSpockvert.xといったG*技術をテーマに、ハンズオンやコードリーディングなど参加型の内容を金曜日に開催しています。

今回のG*ワークショップ"Z" 第19弾は、「G*なWeb API」と題して、昨今話題のWeb APIに関連した以下の内容です。いずれもハンズオン形式ではありません

Grails 3でWeb APIを簡単に作ろう!

Grails 3の新機能の紹介を交えながら、GrailsでのWeb API構築がいかに容易にできるかをお話します。GrailsのREST機能と、Web Microプロファイルなどを含めてご紹介する予定です。

REAL Objects : 動的なエンタプライズAPIアーキテクチャをどう構築するか

REAL Objectsは、(例えばSmalltalkのような) メッセージ・ベースのオブジェクト指向アーキテクチャを現代の並列/分散環境で実現するものです。 現実の業務システムのような、大規模で、ドメイン・モデルが動的/多様で, 非パブリックなAPIを持つアーキテクチャをどう実現するかについて、 G*技術を (多少) 絡めつつ, お話しします (ワークショップ形式ではありません)。

講演者紹介

こちらからお申し込みください。

G*なWeb API - 日本Grails/Groovyユーザーグループ | Doorkeeper

Elmでやってみるシリーズ16: マウスストーカーを実装する

リアクティブプログラミングの技術を用いてマウスストーカーを実装する - はこべブログ ♨」という記事があり、興味深いのでElmのリアクティブプログラミングで似たようなことをやってみました。

全画面表示はこちらから。

コードは以下で、プロジェクト全体はこちらにあります。

import Text
import Window
import Time
import Mouse
import List
import Signal
import Graphics.Element(..)
import Graphics.Collage(..)
import Color(..)
import Signal(Signal,(<~),(~))
import AnimationFrame

-- マウスの座標をCollageの座標に変換するいつもの関数
mousePos : Int -> Int -> Int -> Int -> (Float, Float)
mousePos x y w h = (toFloat x-(toFloat w/2)
                   , -(toFloat y)+(toFloat h/2))

-- ★の表示
star : Int-> Int -> (Int, Int) -> Form
star w h (x, y) = Text.fromString "★"
                        |> Text.color orange
                        |> Text.centered
                        |> toForm
                        |> move (mousePos x y w h)

-- ビューの定義
view : List (Int, Int) -> (Int, Int) -> Element
view posList (w,h) = collage w h (List.map (star w h) posList)

-- ★の座標のリストのSignalを作る
stars : Signal(List (Int, Int))
stars = let 
          trace = Time.delay 100 -- 100ms遅延を与えたSignalを生成
          p1 = Signal.sampleOn AnimationFrame.frame Mouse.position -- 最初はマウス座標を追う
          p2 = trace p1 -- 以降、一個前の座標を追うようにする
          p3 = trace p2
          p4 = trace p3
          p5 = trace p4
          p6 = trace p5
          p7 = trace p6
          p8 = trace p7
          p9 = trace p8
          p10 = trace p9
          p11 = trace p10
          p12 = trace p11
          p13 = trace p12
          p14 = trace p13
        in (\a b c d e f g h i j k l m n -> [a, b, c, d, e, f, g, h, i, j, k, l, m, n])
         <~ p1 ~ p2 ~ p3 ~ p4 ~ p5 ~ p6 ~ p7 ~ p8 ~ p9 ~ p10 ~ p11 ~ p12 ~ p13 ~ p14

-- 表示関数viewをliftして★の座標をあてがう
main : Signal Element
main = view <~ stars ~ Window.dimensions

非常に簡潔です。

説明と注意点など

  • Mouse.positionおよびTime.delayが、シグナル(beacon.jsでいうEventStreamに相当)を生成しています。ちなみに次期Elm 0.15では、Signalは名称変更(Stream/Varyingに分割)が検討されているようです
  • 上記コードではマウス位置のサンプリング間隔を効率化するために AnimationFrameというコミュニティパッケージを使用しています。なので他のパッケージを使用する機能がないtry-elmshare-elmでは実行できません。sampleOnをfps 60とかですれば、もしくはsampleOnを使用せず直接Mouse.positionを使用すれば、AnimationFrameへの依存は除去できます。
  • 制限としては、ElmのCanvasベースのAPIであるGraphics.Collageを用いているため、★がifameの枠外まで追随することはありません。Nativeやportを用いてJSと連携すれば、真のマウスストーカーができるかもしれませんが試してはおりません。ちなみに次期版0.15ではJSとの連携能力が大きく変わるようです。
  • 上記コードでp1〜p14を列挙していますが、例えばmapを使用してリストを生成していないのは、Signalは動的に生成できないからです。Elmでは、すべてのSignalの値の依存関係に対応する「Signalグラフ」というものがコンパイル時に静的に決定されます。そのこともあり、不定件数の「Signalのリスト(List Signal a)」というものは作成できたとしても、(Signal (List a))に変換できないのです。sequence :: [m a] -> m [a] が欲しいところですが、そういう関数はなく、定義することも型制約*1で意図的に禁止されています。

余談

もうすぐ出るらしいElm 0.15は、非常に楽しみです。

参考リンク

Elmシリーズの他のエントリはこちら

*1:Signalはアプリカティブでモナドではない。

Grails/Groovyのサイトを構築している、名もなき静的サイトジェネレータ

Grails/Groovyのサイトは、静的サイトジェネレータで作成されています。Groovyサイトは去年ぐらいからそうだったのですが、最近Grailsもそうなりました*1

しかしこの静的サイトジェネレータの名前がわかりません。ソースコード上は、単に「generator/SiteGenerator.groovy」で、独立したgithub projectもありません。groovy-websitegrails-static-websiteというプロジェクトの一部としてそれぞれでカスタム化されて機能しているというだけです。

SiteGenerator.groovyは139行ぐらいでやってることもシンプルで自明なので、独立にするまでもない、ということでしょうか。処理の実行はgradleタスクでguild.graldeに書いてあります。

gradle genrateSite

こんな感じでsite/build/site配下にHTMLなどを含むサイト全体の情報が生成されます。gradle webzipでリンク切れチェックなどが実行された上で、サイト内容をzipで固めたものが生成されます。デプロイ機能とかはないみたいです*2

この名もなき「サイトジェネレータ」がたいへん気に入ったので紹介します。

Markup Template Engineでページを作成する

Markup Template Engineは、Groovy 2.3で導入された、HTMLを書くためのDSLであり、HTML生成を実行するGroovy標準クラスライブラリです。ドキュメントはこちらAPIリファレンスはこちら

たとえばGrailsサイトの、pages配下にあるトップページindex.groovyは以下のような感じで記述されます。

import model.Event

layout 'layouts/main.groovy', true,
        pageTitle: 'The Grails Framework',
        mainContent: contents {
            div(id: 'band', class: 'band') {

            }
            div(id: 'content') {
                include unescaped: 'html/index.html'

                int eventsPerRow = 3
                def allEventNames = allEvents.keySet() as List
                int rows=allEventNames.size()/eventsPerRow + (allEventNames.size()%eventsPerRow != 0 ? 1 : 0)
                for(int rownum = 1; rownum <= rows; rownum++) {
                  int offset = (rownum-1) * eventsPerRow
                  def cssClasses = "row colset-3-article"
                  if(rownum==1) {
                    cssClasses += " first-event-row"
                  }
                  if(rownum==rows) {
                    cssClasses += " last-event-row"
                  }
                  section(class: cssClasses) {
                      if(rownum == 1) {
                        h1 { strong "Groovy and Grails events you shouldn't miss!" }
                      }
                      allEventNames[offset..(Math.min(offset + (eventsPerRow-1), allEventNames.size()-1))].each { String eventName ->
                          Event event = allEvents[eventName]
                          article {
                              div(class: 'content') {
                                  // Note that the event image should be 257x180 to look nice
                                  div(class: 'event-img', style: "background-image: url(${event.logo})") {}
                                  h1 {
                                      a(href: event.url) {
                                          strong eventName
                                          br()
                                          em event.location
                                      }
                                  }
                                  time event.date
                                  yieldUnescaped event.description
                              }
                          }
                      }
                  }
                }
            }
            include unescaped: 'html/they-use-groovy.html'
        }

要するに、HTMLをプログラムとして書くわけです。抽象化、モジュール化、共通部分の括りだし、合成、型チェックなどなど、プログラミング言語が持つフル機能を享受してサイトというシステムの提供機能を記述することができます。

Markup Template Engineについてはこちらの資料もどうぞ。

大規模なサイトは、構造をもったソフトウェアなのであり、「プログラムのように」ビルドやチェックができるのが利点です。さらに静的サイトジェネレータ一般の利点として、ソースコード管理システムで管理できるのがうれしいです。データをDBに保存するCMSとかは個人的には捨てるしかないです。バックアップとか面倒すぎるし、履歴を辿れないのもアレだし。かといってGititとか使ってみたこともありますが、結局私には耐えられなかったとです。

生のHTMLを使える

先のindex.groovyでは、include unescaped "html/index.html"のようにHTML断片を使えます。

これは実際問題としては現実的です。Excelをたとえば XLS2HTMLTABLEアドインなどでテーブルに変換したりしたものをページの一部に貼る、みたいな状況ではHTMLを「そのまま」使いたいときがあり、必須です。半端なwikiとか、「不完全なHTML」だけしかつかえないCMSなどは、動的だろうが静的だろうが捨てるしかないと私は思うわけです。

「 データ」をモデルクラスとして定義、使用する

わたしはこれが一番面白いと思ったのですが、サイトで使用するデータを「モデル」として定義します。たとえば以下が「モデル」です。

  • メニューの選択項目(リンクであったり、サイト内のページであったり)
  • 「書籍」一覧を含むのであれば、書籍クラス(Book.groovy)
  • サイトで配布物jarを列挙しているのであれば、配布物のクラス(Distribution.groovy)

さらにこれらのモデルは、sitemap.groovyというDSLで、その値を簡潔に定義できます。 sitemap.groovyの例はこちら

Webサイトが大規模でも大規模じゃなくても、静的であっても、それがデータを扱っていることは間違いありません。静的データをHTMLのULやliでベタに書くということは、もしやたいへんに愚かしいことではなかったのか、と考え込まずにはいられません。JSで書くのもアレだし。

こちらのサイトによれば、sitemap.groovyとモデルクラスで設定するモデル群は、「低コストなデータベース」だそうです。 まさしく。コンパイル時に使用したい、データベースか。うーん、この発想はなかったな。

もちろん、既存の静的ジェネレータでも「ブログエントリ」とかはデータなんですが、もっと汎用的なもの。

リンク切れチェック

checkDeadLinksタスクでサイト全体のリンク切れのチェックが実行されます。

事前インストールフリー

Gradleだから当たり前なんですが、サイトのプロジェクトソースさえ入手すれば、Java以外のインストールなしに、GroovyやGradle、gvmやらの明示的なインストールなしで、即刻ページ生成が可能となります。gradle wrapperってやつです。

その他

font-awesomeとかtwitter-bootstrapとかをデフォルトでだいたい含んでます。

まとめ

Spud-CMSGrailsプラグインとかも遍歴しましたが、社内のサイトとかは、これからは当面これで作っていこうと思います。apacheだけでいいです。tomcatとかもういいです。

*1:ただし、一部はGrailsアプリケーションとして動作しているそうです。たぶんプラグインのページ。

*2:もちろんすぐ作れます。Gradleだし!

Elmでやってみるシリーズ15: Json.Decodeで9個以上のフィールドを持つobjectをデコードしてみる

本記事では、ElmのコアライブラリにおけるJSONデコードパッケージJson.Decodeを用いて、9個以上のフィールドをもつオブジェクトのデコード方法について説明する。

2015 08 31追記あり(andMapを使用する)

その前に

その前にJson.Decodeを簡単に説明する。 Json.Decodeは、JSONのデコード処理をするためのコアライブラリの一つである。Elm 0.14以前のものとは完全に置き換られている*1

Jsonデコード処理の概要

一般形式としては、

   decodeString : Decoder a -> String -> Result String a
   decodeString <デコーダ> <JSON文字列> = ...

である。decodeStringは、第二引数<JSON文字列>をJSON文字列としてパースし、<デコーダ>で指定した型のデコード結果を返却する。デコーダは1つの型パラメタaをとる多相型(Decoder a)の値であり、decodeStringにDecoder a型のデコーダを与えて呼び出したとき、パースが成功していたら、a型の結果がOk aとして返る。型の不一致やパース失敗のときはErr Stringが返る(Resultの型は、type Result error value = Ok value | Err error)。

Json.Decodeにおいて、JSON文字列からのパース処理は、基本的にdecodeStringから始まる。

Jsonデコード処理の例

以下はJsonデコード処理の例である。

import Json.Decode (..)
import Text (..)
import Graphics.Element (flow,down)
import List

main = flow down 
       [ asText <| decodeString string "\"abc\"" --(1)
        , asText <| decodeString int "3" --(2)
        , asText <| decodeString (list string) "[\"abc\",\"def\"]" --(3)
        , asText <| decodeString (list bool) "[true,false,true]" --(4)
        , asText <| decodeString (array bool) "[true,false,true]" --(5)
        , asText <| decodeString (tuple3 (,,) bool int string) "[true, 1, \"abc\"]" --(6)
        , asText <| decodeString ("a" := int) "{\"a\":1, \"b\":2}" --(7)
        , asText <| decodeString ("c" := int) "{\"a\":1, \"b\":2}" == Err ("expecting an object with field 'c' but got {\"a\":1,\"b\":2}") -- (8)
        , asText <| decodeString (object2 (,) ("a" := int) ("b" := string)) "{\"a\":1, \"b\":\"foo\"}" --(9)
        , asText <| decodeString (at ["a","b"] int) "{\"a\":{ \"b\":2 } }" --(10)
        , asText <| List.map (\it -> (decodeString it "{\"a\":1, \"b\":2}" )) [("a" := int), ("b" := int)] --(11)
       ]
  • (1),(2)におけるstringintなどはデコーダ型の値である。
  • (3),(4),(5)におけるlist, arrayなどはデコーダ型の値をとってデコーダを返す関数である。いずれもJson.Decodeで定義されている。list、arrayはいずれもJSONの配列からデコードするデコーダであるが、listは結果としてList型を、arrayはArray型の値を取り出す(デコードする)。このように、デコーダは「取り出したい型、結果として得たい型」を与えるものである。JSONの配列の要素の型は一致している必要がある。
  • (6)のtuple3は、3要素のJSONの配列と、それぞれの配列要素を引数としてとる3引数の関数を引数として与えると、その関数の結果をデコード型として得ることができる。「touple*」という名称だが、デコード結果として得ることができるのはタプル型に限らない。拡張レコード型だろうがリストだろうが別の関数を呼び出した何かの処理結果であろうが、なんでも御座れである。ただしたとえば返したい型がElmリストの場合は、デコード対象のJSONの配列のすべての要素の型が一致していないとデコードに失敗するであろう。
  • (7)の(a := int)は、オブジェクトのフィールドa(Int型)をデコードするデコーダである。該当するフィールドが存在しなければErrが結果として得られる(8)
  • (9)複数のフィールドを同時にデコードするにはobject<N>(Nは1〜8)を用いる。ここではint型フィールドaとString型フィールドbをデコードし、結果としてタプル(Int,String)の値を返している。
  • (10)atは、指定したフィールドaがオブジェクト型である場合、そのオブジェクトのフィールドbを取得して…のようにネストしたオブジェクトを順に辿っていくためのデコーダである。

9個以上のフィールドのデコードの問題

さて、オブジェクトをデコードする場合、上記(9)で使用したように、object<N>という関数を使用すると良い。問題は、Nが8までのしか定義されていないことである。少なっ!

9個以上のフィールドをデコードするにはどうしたら良いであろうか。一つの案は、ちまちまと:=を用いて、1個ずつフィールドを取り出せばよい。あるいは(11)のように何個かの:=に対してList.mapで一気にdecodeStringの結果を得ることもできる。しかし、decodeStringがパース処理の呼び出しそのものであり、効率が悪いという問題がある*2。さらに、結果が、Resultの配列なのも気になる。どれか1個がErrなら、全体がErrになってほしい。

この問題を解決する方法を以下に示す。

9個以上のフィールドをデコードする方法

まず、JSONのデコード結果として以下のような拡張レコードで結果を得たいものとする。タプルでも良いのだが、とりあえず。

type alias MyData = {
    f1 : Maybe String
  , f2 : Maybe Int
  , f3 : Maybe Float
  , f4 : Maybe String
  , f5 : Maybe String
  , f6 : Maybe String
  , f7 : Maybe String
  , f8 : Maybe String
  , f9 : Maybe String } -- 9個以上

そして、以下のようにヘルパ関数filedを準備する。

field : Decoder a -> String -> Dict.Dict String Value -> Maybe a
field typ fld dic = case (case Dict.get fld dic of -- (*)
                            Just val -> decodeValue (maybe typ) val
                            Nothing -> Err fld) of
                      Ok res -> res
                      Err _ -> Nothing

ここで(*)での「Dict.get fld dic」で得られるのは、Json.Decode.Value型の値で、そこから実際の値をとりだすためにdecodeValueを使用する。ここでのdicは後述の「dict value」デコーダを用いることで得られる、「JSONオブジェクトのデコード結果としてのDictの値」である。あとはちまちまとResultやMaybeでラッピングされた値をパターンマッチングでほどいて、最後にまたMaybeでラッピングしている。

このヘルパ関数を以下のように使用して、MyDataをデコードするデコーダを定義する。

myDataDecoder : Decoder MyData
myDataDecoder = customDecoder (dict value) (\dic ->
                                     Ok {
                                            f1 = field string "f1" dic
                                          , f2 = field int "f2" dic
                                          , f3 = field float "f3" dic
                                          , f4 = field string "f4" dic
                                          , f5 = field string "f5" dic
                                          , f6 = field string "f6" dic
                                          , f7 = field string "f7" dic
                                          , f8 = field string "f8" dic
                                          , f9 = field string "f9" dic
                                     })

deocdeStringに、デコーダdict value」を与えることが、肝心要の要点である。得られるのはキーがString、バリューがDict Json.Decode.Value型のDict(辞書)型の値のデコーダである。ちなみに、Json.Decode.Valueは、JSONのプリミティブや配列やObjectなどを直和したADTではなく、Valueという型引数も持たない、単一のデータコンストラクタだけを持つ型である。そんな型が何の役にたつかというと、これは実はNativeのJavaScriptの中だけで意味を持つものであり、Java Script側ではJSONのノードを保持している。Json.Decode.Valueは、本来その存在を気にする必要はないようにAPIが設計されているのだが、今回のように9個以上のフィールドを一気にデコードする、とかこういうときにはおおいに気にすることになるわけである。

上記デコーダmyDataDecoderの使い方は以下のとおり。

        decodeString myDataDecoder "{\"f1\":\"abc\",\"f2\":3,\"f3\":3.3,\"f4\":\"s\",\"f5\":null,\"f6\":\"s\",\"f7\":\"s\",\"f8\":\"s\",\"f9\":\"s\"}"

全体のコードはShare-elm上にも置いたが、以下のとおり。

import Json.Decode (..)
import Text (..)
import Graphics.Element (flow,down)
import List
import Dict

type alias MyData = {
    f1 : Maybe String
  , f2 : Maybe Int
  , f3 : Maybe Float
  , f4 : Maybe String
  , f5 : Maybe String
  , f6 : Maybe String
  , f7 : Maybe String
  , f8 : Maybe String
  , f9 : Maybe String } -- 9個以上

field : Decoder a -> String -> Dict.Dict String Value -> Maybe a
field typ fld dic = case (case Dict.get fld dic of
                            Just val -> decodeValue (maybe typ) val
                            Nothing -> Err fld) of
                      Ok res -> res
                      Err _ -> Nothing

myDataDecoder : Decoder MyData
myDataDecoder = customDecoder (dict value) (\dic ->
                                     Ok {
                                            f1 = field string "f1" dic
                                          , f2 = field int "f2" dic
                                          , f3 = field float "f3" dic
                                          , f4 = field string "f4" dic
                                          , f5 = field string "f5" dic
                                          , f6 = field string "f6" dic
                                          , f7 = field string "f7" dic
                                          , f8 = field string "f8" dic
                                          , f9 = field string "f9" dic
                                     })

main = flow down 
       [ asText <| decodeString string "\"abc\""
        , asText <| decodeString int "3"
        , asText <| decodeString (list string) "[\"abc\",\"def\"]"
        , asText <| decodeString (list bool) "[true,false,true]"
        , asText <| decodeString (array bool) "[true,false,true]"
        , asText <| decodeString (tuple3 (,,) bool int string) "[true, 1, \"abc\"]"
        , asText <| decodeString (tuple3 (\a c b->(a,b,c)) bool int string) "[true, 1, \"abc\"]"
        , asText <| decodeString ("a" := int) "{\"a\":1, \"b\":2}"
        , asText <| decodeString ("c" := int) "{\"a\":1, \"b\":2}" == Err ("expecting an object with field 'c' but got {\"a\":1,\"b\":2}") -- (9)
        , asText <| decodeString (object2 (,) ("a" := int) ("b" := string)) "{\"a\":1, \"b\":\"foo\"}" --(9)
        , asText <| List.map (\it -> (decodeString it "{\"a\":1, \"b\":2}" )) [("a" := int), ("b" := int)]
        , asText <| decodeString (at ["a","b"] int) "{\"a\":{ \"b\":2 } }"
        , asText <| decodeString myDataDecoder "{\"f1\":\"abc\",\"f2\":3,\"f3\":3.3,\"f4\":\"s\",\"f5\":null,\"f6\":\"s\",\"f7\":\"s\",\"f8\":\"s\",\"f9\":\"s\"}"
        , asText <| decodeString myDataDecoder "[]" == Err ("expecting an object but got []")
       ]

実行結果は以下のようになる。

Ok "abc"
Ok 3
Ok ["abc","def"]
Ok [True,False,True]
Ok (Array.fromList [True,False,True])
Ok (True,1,"abc")
Ok (True,"abc",1)
Ok 1
True
Ok (1,"foo")
[Ok 1,Ok 2]
Ok 2
Ok { f1 = Just "abc", f2 = Just 3, f3 = Just 3.3, f4 = Just "s", f5 = Nothing, f6 = Just "s", f7 = Just "s", f8 = Just "s", f9 = Just "s" }
True

他にも良いやりかたがあったらおしえてちょう!(NativeでやるとかPortでやるのは無しとして)

まとめ

decodeStringは内部でまずはJSONをパースして、いったんかならずJson.Decode.Valueのツリーが形成されているはず。なので、そこに対してデコーダをかませていく処理は、「ツリーをパースするVisitorパターン」でありDecoderがビジターである。そしてそのVisitor=Decoderは、パーサコンビネータライブラリのように合成していく。わかってしまえばなんということはない。 ない、のだが、動的言語とかでJson扱うのとは別次元の面倒さが発生していることは否定できないなー。

しかし、8は少ないだろ、8は…。

(追記) プルリクを打ってみた: object decoder for more than 8 fields · uehaj/core@3d49629 · GitHub

(追記2)

メーリングリストで以下のやりかたを教えてもらった。

myDataDecoder =
         ("f1":=int) `andThen` (\x1 ->
         ("f2":=string) `andThen` (\x2 ->
         ("f3":=float) `andThen` (\x3 ->
         ("f4":=string) `andThen` (\x4 ->
         ("f5":=maybe string) `andThen` (\x5 ->
         ("f6":=string) `andThen` (\x6 ->
         ("f7":=string) `andThen` (\x7 ->
         ("f8":=string) `andThen` (\x8 ->
         ("f9":=string) `andThen` (\x9 -> succeed {f1=x1,f2=x2,f3=x3,f4=x4,f5=x5,f6=x6,f7=x7,f8=x8,f9=x9})))))))))

Decoder.andThenはモナドのbind(>>=)です。なるほどねー。 でもこれはこれで見にくいし書きにくい。 do記法があれば

myDataDecoder = do
         x1 <- ("f1":=int)
         x2 <- ("f2":=string)
         x3 <- ("f3":=float)
         x4 <- ("f4":=string)
         x5 <- ("f5":=maybe string)
         x6 <- ("f6":=string)
         x7 <- ("f7":=string)
         x8 <- ("f8":=string)
         x9 <- ("f9":=string)
         succeed {f1=x1,f2=x2,f3=x3,f4=x4,f5=x5,f6=x6,f7=x7,f8=x8,f9=x9}

こう行ってたとこなんだけどね。 あるいは<~と~(Haskellの<$><*>)を定義して、

myDataDecoder = (\x1 x2 x3 x4 x5 x6 x7 x8 x9 -> {f1=x1,f2=x2,f3=x3,f4=x4,f5=x5,f6=x6,f7=x7,f8=x8,f9=x9})
                             <~ ("f1":=int)
                               ~ ("f2":=string)
                               ~ ("f3":=float)
                               ~ ("f4":=string)
                               ~ ("f5":=maybe string)
                               ~ ("f6":=string)
                               ~ ("f7":=string)
                               ~ ("f8":=string)
                               ~ ("f9":=string)

こう。

andMapを使う方法(追記2015/08/31)

以下で行けます。これが今のところベストなやりかたと思う。 参考: https://groups.google.com/d/msg/elm-discuss/J9ip8MqXtYM/u4aRGAlbSkcJ

import Json.Decode as Decode
import Json.Decode exposing (..)
import Text exposing (fromString)
import Graphics.Element exposing (flow,down,show)
import List

type alias MyData = {
    f1 : String
  , f2 : Int
  , f3 : Float
  , f4 : String
  , f5 : Maybe String
  , f6 : String
  , f7 : String
  , f8 : String
  , f9 : String }

andMap : Decoder (a -> b) -> Decoder a -> Decoder b
andMap = Json.Decode.object2 (<|)

myDataDecoder2 : Decoder MyData
myDataDecoder2 =
    MyData `Decode.map` ("f1":=Decode.string)
           `andMap` ("f2":=Decode.int)
           `andMap` ("f3":=Decode.float)
           `andMap` ("f4":=Decode.string)
           `andMap` ("f5":=Decode.maybe Decode.string)
           `andMap` ("f6":=Decode.string)
           `andMap` ("f7":=Decode.string)
           `andMap` ("f8":=Decode.string)
           `andMap` ("f9":=Decode.string)

main = flow down
       [ show <| decodeString myDataDecoder2 "{\"f1\":\"abc\",\"f2\":3,\"f3\":3.3,\"f4\":\"s\",\"f5\":null,\"f6\":\"s\",\"f7\":\"s\",\"f8\":\"s\",\"f9\":\"s\"}"
       ]

リンク

*1:以前のものの使用例はこちらでも紹介しているが、無用のものとなった

*2:本当に何度も呼びだすかは未確認。でもたぶん呼んでる。Elmにおいて、引数が同じ文字列で、純粋関数だからメモ化される、という期待はできない。さらにもちろんElmは遅延評価ではなくサンクなんかない。Signalにliftされた関数からsignal値としてのJSONが引数に与えられているなら、JSON文字列の更新がなければ多数回呼び出されないと考えてよいが、ここで考えている例の場合はあてはまらない。

Elmにおけるimportの使い方

Elmにおけるインポートの方法を説明する。本説明が対応するElmのバージョンはElm 0.14かそれ以降、ただしインポートの仕様は今後大きく変更される可能性がある(今後についての関連情報)。

他モジュールで定義され、エクスポートされた識別子(型コンストラクタ、値コンストラクタ、型エイリアス、定数・関数など)は、インポートすることで始めて使用可能となる。Elmのimportは、この点でJavaのimportと異なっている。Javaではimport宣言をしてもしなくてもFQCNを指定すればクラスパスにあるすべての識別子を参照可能であるが、Elmでは、インポートの明示的な宣言をしない限り、他モジュールの識別子を利用できない(ただし、いくつかのモジュール・識別子はデフォルトでインポートされている。後述)。

インポートの宣言をする方法は大きく分けて2つある。

  • Qualified Import
  • Open Import

(Open Importはunqualified importと呼ばれることもあるようだ)。いずれもソースファイル冒頭(モジュール宣言のすぐ後)に記述する。以降、それぞれについて説明する。

Qualified Import

一般形式:

  1. import モジュール名A
  2. import モジュール階層.モジュール階層.モジュール名B
  3. import モジュール名 as 短縮識別子C
  4. import モジュール階層.モジュール階層.モジュール名 as 短縮識別子D

説明:

Qualified Importは、モジュール指定もしくは短縮識別子をプリフィックスとして、他モジュールからexportされた識別子を利用する。たとえば、上記のようにimportを行ったとき、以下のように識別子をアクセスできる。

  1. モジュール名A.識別子
  2. モジュール階層.モジュール階層.モジュール名B.識別子
  3. 短縮識別子C.識別子
  4. 短縮識別子D.識別子

3,4の「短縮識別子」はこの記事を書く際に便宜上付けた名前であり正式名称は不明。 短縮識別子は短かい必要はないが1文字が多く使われる(Signal→Sなど)。4.のように階層のある名前例えば「Graphics.Element.Field」に「Field」という短縮識別子を付けるなどもできる。

短縮識別子は同じものを複数回指定できないので、Qualified Importでは識別子の衝突が発生しない。(モジュール名があらかじめ衝突していない前提であるが。)

中置演算子のインポートは、Qualified Importではできない。後述のOpen Importを使用する必要がある。

例1:

import Text

main=Text.plainText "abc"

例2:

import Text
import Graphics.Element

main=Graphics.Element.flow Graphics.Element.right [ Text.asText "ABC" ]

例3:

import Text as T
import Graphics.Element as Element

main=Element.flow Element.right [ T.asText "ABC" ]

例4

import Text(asText)
import Signal as S -- 短縮識別子S
import List as L -- 短縮識別子L
import Signal(..)

main =  S.map asText <| constant ( L.map (\x -> x+1)   [1,2,3]  )

Open Import

一般形式:

  1. import モジュール名A(..)
  2. import モジュール階層.モジュール階層.モジュール名B(..)
  3. import モジュール名C(識別子,識別子,識別子…)
  4. import モジュール階層.モジュール階層.モジュール名D(識別子,識別子,識別子…)

説明:

Open Importは、指定したモジュールで定義・エクスポートされた識別子をすべて(1,2のケース)あるいは列挙したもの(3,4のケース)を、importする側の名前空間に取り込んで、前置指定も無く利用できるようにする。識別子の衝突が発生する可能性があり、その可能性は、1,2>3.4である。

例5

import Text(..)

main=plainText "abc"

例6

import Text(..)
import Graphics.Element(..)

main=flow right [ asText "ABC" ]

例7

import Text(asText)
import List((::)) -- 中置演算子::のOpen Import
import Signal
import Signal(constant) -- Qualified ImportとOpen Importを組合わせる

main=(Signal.map) asText (constant (2::[3,4,5]))

型のOpen Importについて

判別共用体型(Tagged Union, ADT)は、「型コンストラクタだけをimportして値コンストラクタをインポートしない」「指定した値コンストラクタだけをインポートする」などが可能である。

Qualified ImportとOpen Importの使い分け

簡潔さの程度でいうと以下の順である(上の方ほど字数が少ない)。

  1. 識別子を指定せずにOpen Import
  2. 識別子を指定してOpen Inport
  3. Qualified importで短縮識別子使用
  4. Qualified importで短縮識別子使用しない

mapなどいかにも重なりそうな名前があるので、書き捨てコード以外は3か4、最悪でも2を選んでおくのが安全ではあるが、衝突は滅多に無い気もするな…はて。出現頻度が高いものは簡潔に書きたいので、importするモジュールごとに、そして書く内容によってそれぞれ判断したい気もする。elm-htmlとか既存のコードでどうやってるかを参考にするのも良いのではないかと。

デフォルトのimport

任意のElmプログラムで、以下が指定されているのと同じ動作をする。 (参考の下の方 )

  • import Basics (..)
  • import List ( List )
  • import Maybe ( Maybe( Just, Nothing ) )
  • import Result ( Result( Ok, Err ) )
  • import Signal ( Signal )

Listの::や、Signalの<~, ~が(Elm 0.14では少なくとも)デフォルトインポートされていないことに注意。前はElementとかSignal(..)などもデフォルトインポートされてたんだけどな…(´・ω・`)。

おまけ:

割と最近出た書籍「Seven More Languages in Seven Weeks: Languages That Are Shaping the Future」にはElmの章があるようです。ぽちっとな。

Seven More Languages in Seven Weeks: Languages That Are Shaping the Future
Bruce A. Tate Frederic Daoud Ian Dees Jack Moffitt
Pragmatic Bookshelf
売り上げランキング: 9,168

関連リンク

「Elmでやってみる」シリーズのまとめエントリ - uehaj's blog

「Elmでやってみる」シリーズのまとめエントリ

このブログ記載のElmの記事で、動作するElmの実行プログラムをiframeで貼っていたのですが、いくつも動かなくなっていたので、動くようにしました。 ちなみに動かなくなっていた原因は2つありました。

  • Elmのコード共有サイトshare-elmの仕様がかわった。対処としてはリンクをはりなおしました。
  • elm-runtimeへのリンクが、src="http://elm-lang.org/elm-runtime.jsで参照していたのが、404エラーになるようになったため。そうなった正確な理由は不明ですが、バージョンに依存するはずなので、いずれにせよこの絶対リンクが上手くうごかなくなるのはあきらかでした。対処としてはgithub-page上の固有のものにリンクをはるようにした。

たいへん失礼しました。以下のとおりです。今はすべて動くはずです。

Elm関連記事へのリンクは、これからはこのエントリにも追記していきます。

書籍紹介

「Seven More Languages in Seven Weeks: Languages That Are Shaping the Future」にはElmの章があります。Elmの概要や作者インタビューなどもさりながら、他のどマイナー言語(IdrisやFactor, Julia, miniKanren等)の紹介も興味深かったです。

Seven More Languages in Seven Weeks: Languages That Are Shaping the Future
Bruce A. Tate Frederic Daoud Ian Dees Jack Moffitt
Pragmatic Bookshelf
売り上げランキング: 9,168

enumに継承を! traitとenumの妙な関係、もしくはGrailsのドメインクラスの選択フィールドを国際化表示するのにtraitが便利

Grailsドメインクラスにおいて、いくつかの候補の数値のいずれか、というフィールドを作成し、scaffoldで生成した画面からCRUD操作したいとします。

簡単なのは、こうですね。

class Domain {
  Integer something

  static constraints = {
     something inList:[1,2,3]
  }
}

しかし数値フィールドに量としてではなく個々の値にそれぞれの意味がある場合、たとえば、腹の状態を表すフィールドの値として、

  • 1: はらぺこ
  • 2:まんぷく
  • 3:こばらがへった

を表現するようなものだったとします。このとき、すくなくとも、Scaffoldの画面上では数値ではなく意味のわかる文字列で表示したり選択入力させたいわけです。しかしデータそのものを文字列にするのもDB上の表現をそうしたくないのでいやだとします*1
ということでenumの出番です。

class Domain {
  enum OnakaStatus {
     STARVATION(1),
     SATICEFIED(2),
     SOSO(3)
     private final int id;
     private OnakaStatus(int id) {
         this.id = id;
     }
     public int getId() {
        return id;
      }
  }
  OnakaStatus onakaStatus
  static constraints = {
//     onakaStatus inList:[OnakaStatus.STARVATION,OnakaStatus.SATICEFIED,OnakaStatus.SOSO]
// よく考えるとこんなconstraintはいらない。enumなんだから。
  }
}

"STARVATION","SATICEFIED","SOSO"を選択肢とするセレクト/オプションタグをscaffoldが生成し、FORMパラメータとしてはその文字列、DB中には対応する数値(1,2,3)が保存されます。ナイス! GORMナイス!
でもこれだと表示が日本語にならないし。"STERVATION"の代りに"はらぺこ"というenumの識別子を使えば表示が日本語にはなるが、FORMパラメータとしてその日本語文字列が使われるのは好きくない。ではこうしてみてはどうか。

class Domain {
  enum OnakaStatus {

     STARVATION(1, "はらぺこ"),
     SATICEFIED(2,"まんぷく"),
     SOSO(3,"こばらがへった")
     private final int id;
     private final String value
     private OnakaStatus(int id, String value) {
         this.id = id;
         this.value = value;
     }
     public int getId() {
        return id;
      }
     String toString() {
         return value
     }
  }
  OnakaStatus onakaStatus
}

一応これで識別子は英字のまま表示を日本語にできます。しかしながら、国際化対応ができません。
ぐぬぬ
そういう場合は、enumにorg.springframework.context.MessageSourceResolvableを実装させて、以下のメソッドを定義し、

   enum OnakaStatus implements org.springframework.context.MessageSourceResolvable {
     :
    public Object[] getArguments() { [] as Object[] }
    public String[] getCodes() { [ name() ] }
    public String getDefaultMessage() { "?-" + name() }

   }

この上でi18nメッセージをenumの要素をキーとして用意します。たとえば、grails-app/i18n/なんとか.propertiesに、

STARVATION=はらぺこ
SATICEFIED=まんぷく
SOSO=こばらが減った

でも、enumごとにメソッド追加するの〜! えー冗長。やだーやだー。
ぐぬぬぬ。
共通するメソッドを親クラスに切り出したいところですが、enumはクラスからの継承(extends)はJava/Groovyの言語仕様上許されておりません。interfaceからのimplementsなら可能なのだが。はっ!!
ということでtraitを使えばいいのです。

trait I18nEnum implements org.springframework.context.MessageSourceResolvable {
    public Object[] getArguments() { [] as Object[] }
    public String[] getCodes() { [ name() ] }
    public String getDefaultMessage() { "?-" + name() }
}

こういうtraitを用意して、enumはこうして

    enum OnakaStatus implements I18nEnum {
        STARVATION(1),
        SATICEFIED(2),
        SOSO(3)
        private int id;
        private OnakaStatus(int id) {
            this.id = id;
        }
        public int getId() {
            return id;
        }
    }

あらすっきり*2

enum継承できないのでメソッドを共有させることが本来はできないわけですが、implementsはでき、試したところtraitからもできた。なのでenumメソッドを共有化できました。めでたい。Groovy便利。

Java8のデフォルトメソッドでどうかは不明。

*1:文字列にした上で、フィールドはtransientにしておいて、beforeSaveとかのタイミングで値を適切にさしかえるという手はある。でも国際化の問題はある。

*2:もっとすっきりさせようと、idとgetIdをtraitに移動させてみたり、@InheritConstructorなども試してみたがうまくいかなかった。