uehaj's blog

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

elmでやってみるシリーズ7: elm-htmlでTwitter Bootstrapを適用

Elmでやってみるシリーズ7: elm-htmlでTwitter Bootstrapを適用。

つい先日、「Blazing Fast HTML」と銘打って、elm-htmlライブラリが公開されました。これはElmでDOMツリーを構築・更新するための低レベルライブラリであり、Virtual DOMという技術を使っているので非常に画面更新速度が速いそうです。SPA(Single Page Application)ではDOMの更新速度が重要になりますが、Elmは純粋関数型・イミュータブルデータなのでそのことを利用してさらに効率良く実装できるそうな。

従来、ElmはCSSとの連携はあまり重視されておらず、「ElmはCanvasを使ったアニメーションが得意」とされてきましたが、現代的な見た目のHTMLベースのアプリも自在に開発できるようになる、という道筋の第一歩なわけです。まだ未成熟ですがね。

個人的にTwitter BootstrapなどのCSSフレームワークとElmとの連携に興味があったので、今回試してみました。

やったことは以下のとおり。

  1. elm-getでelm-htmlをインストール
    • elm-get install evancz/elm-html 0.1.2
  2. HtmlTest.elmを作る(後述)
  3. 以下のように--only-jsオプションを付けてelmコードをコンパイル。なお、--only-jsを使用する場合、呼び出すためにモジュール名が必要なので冒頭でmodule宣言が必要になる。
    • elm --make --only-js HtmlTest.elm
  4. Twitter Bootstrapを使用するindex.htmlをこちらの「Embed in HTML」を見て適当につくる。index.htmlの内容はこちらを表示してビューソースしてみてください。
  5. index.htmlとHtmlTest.jsをgithub pagesにpush。

処理の内容としては、マウスのX座標、Y座標の過去10個分をテーブルにして表示するというもの。

コード(HtmlTest.elm)は以下のとおり。

module HtmlTest where -- --js-onlyをする場合モジュール宣言は必須

import Html (..)
import Mouse

-- マウス座標データを一行分の<tr>に変換
data2line : (Int,Int) -> Html
data2line (x,y) = node "tr" [] []
         [ node "td" [] [] [text <| show x]
         , node "td" [] [] [text <| show y]
         ]

-- テーブルを作る
tbl : [(Int,Int)] -> Html
tbl dat = node "table" ["className" := "table table-striped table-bordered table-condensed"] []
      [ node "thead" [] [] [
          node "tr" [] []
              [ node "th" [] [] [text "Mouse X"]
              , node "th" [] [] [text "Mouse Y"]
            ]
          ]
      , node "tbody" [] []
         (map data2line dat)
      ]

-- アンカータグによるリンクを作るユーティリティ関数
linkTo txt url = node "a" ["href":=url] [] [text txt]
-- ボタンのように装飾をしたリンクを作るユーティリティ関数
buttonLinkTo txt url = node "a" ["href":=url,"className":="btn btn-primary btn-lg"] [] [text txt]


-- 画面を作る
body : [(Int,Int)] -> Html
body dat = node "div" ["className":="navbar navbar-default navbar-fixed-top"] ["padding-top":= px 10] [
         node "div" ["className":="container"] [] [
           node "div" ["className":="jumbotron"] []
             [ node "h1" [] [] [ text "Elm/Twitter Bootstrap" ]
             , node "p" [] [] [ "Elm-html" `linkTo` "https://github.com/evancz/elm-html"
                              , text "で"
                              , "Twitter bootstrap" `linkTo` "http://getbootstrap.com/" 
                              , text "連携しています。"]
             , node "p" [] []
             [ "もっと学ぼう" `buttonLinkTo` "http://elm-lang.org/"
             , "ソースコード" `buttonLinkTo` "HtmlTest.elm"
             ]
           ]
           , tbl dat
         ]
       ]

-- 画面を表示する
display : [(Int, Int)] -> Element
display list = body list |> toElement 200 200

-- マウス座標のシグナル値(x,y)をliftして与えて画面を表示する
main : Signal Element
main = display <~ foldp (\it acc -> take 10 (it :: acc)) [] Mouse.position

実行画面(操作可能)

全画面で実行する場合はこちら
プロジェクト全体はこちらのgh-pagesブランチからどうぞ。

気づいたこと

  • elm-htmlはGroovyのマークアップビルダーみたいなもの。
  • elm-htmlの出力はHtml(DOM)であり、それを変更する手段は提供されていない。なのでコード的には毎回全体を何も考えずに宣言的に生成する。しかし、Elmが完全に純粋であることも利用して、Vitual DOMを通じて、実DOMに対して最低限の差分のみが効率良く適用されるという話。
  • elm-htmlの記述はHTMLと一対一対応で、冗長度が高いが、elm-htmlの位置付けは、より高機能でより抽象度の高いライブラリ作成の基盤になるための低レベルライブラリ、というものなのでこれはこれで良い。
  • elm-htmlはいわゆる非標準ライブラリであり、しかも--only-jsでHTMLと連携させるので、share-elmなどでは公開できない。なのでgithub-pagesを使用して公開してみた。
  • --only-jsを前提とするとelm-serverでホットリロードできない。もしくはやりかたがわからない。
  • elm-htmlのHtmlとGraphics.Input(.Fields)との連携・関係はまだ理解できていない。elm-htmlだけでhandleを使えるから、こっちで閉じてやるんだろうか。そうじゃないとしたらElementやfieldをHtmlに入れる仕組みが必要なはずだが。
  • elm-htmlではcssも要素の属性も基本的には文字列でしかない。本来なら強い型付けをして欲しいものですが、将来開発されるであろうelm-html上に構築される何かに期待。
  • 合成と共有部分の切り出し、共有化、抽象化というプログラミングの本質的な強力さが現状でも享受できる。html,js,cssを使っていたWebアプリ開発暗黒時代の夜明け
  • Elmで外付けスタイルシートを定義することはできない。本来はElmがLESSやSassの代替になって欲しいものである。*1
  • CSSクラスの指定は「"className":=」、floatは「cssFloat」とするらしい。このような違いはJavaScriptでDOMをいじる場合と同様らしい。

関連エントリ


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



すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

*1:と思ったが、そもそも、DOMを直接いじってスタイルが変更できるのだから、外付けスタイルシートなどはまったくいらなくなる…かとも思ったが、JSでトラバースしてスタイルつけるより、クラス名だけつけてブラウザに任せた方が速いと思うので、やっぱり何かしら必要かも。よくわからない。

elmでやってみるシリーズ8: 赤いドットが回っているかのように見えるけど実は直進運動な錯視

Elmでやってみるシリーズ8: 赤いドットが回っているかのように見えるけど実は直進運動な錯視

少し前に、「このくるくる回る白いドット、実は真っ直ぐ往復してるだけなんだぜ」という記事がありましたが、これをElmで再現してみようというのが今回のネタ。
(追記 id:waman さんが数学解析をされています→回転しているようにみえる白いドットは単振動している - 倭算数理研究所 。このネタ自体もwamanさんのTwitterで知ったんですけどね。)

以下は画面キャプチャ(静止画)。
f:id:uehaj:20140727232140p:plainf:id:uehaj:20140727232055p:plain

コードは以下のとおり。

fps_=96 -- 画面更新頻度
pi2 = 2*pi -- 2π
deg30 = pi2 / 360 * 30 -- 30°をラジアン単位に変換

-- 1秒間にfps_回、1増加するシグナル値。
ticks : Signal number
ticks = foldp (\it acc->acc+1) 0 (fps fps_)

-- ticksから派生させて、1秒間に一回転に対応する角度値のシグナルを計算。また、元シグナルticksに対してdミリセカンド分、発生するのが遅れるシグナルにする
sig : Float -> Signal Float
sig d = let f x = (x / fps_) * pi2
        in f  <~ (delay (d * millisecond) ticks)

-- 座標(x,y)を原点を中心に左にtだけ回転させた座標を得る
rotate_ : Float -> (Float, Float) -> (Float, Float)
rotate_ t (x,y) = (x*(cos t)-y*(sin t),  x*(sin t)+y*(cos t))

-- 時間に追随するラジアン単位角度tに対するsin tの位置を、特定角度xで全体を傾けてプロットする
dot : Float -> Float -> Form
dot t x =  move (rotate_ x (100 * (sin t), 0))  (filled red (circle 3))

-- 「tick値と、角度を変えたdotを6つはっつけたコラージュ(400x200)を作る純粋関数disp」に、dotの位置を表わすシグナル値sigを6つ作ってリフト適用。これらのシグナル値への引数としてそれぞれディレイ値を適切に設定することで、「遅れ」による位相差が生じ、「回転する」かのように見えることになる。
main : Signal Element
main = let disp t p1 p2 p3 p4 p5 p6 = flow down
                [ asText t
                , collage 400 200
                                  [ dot p1 <| deg30*0
                                  , dot p2 <| deg30*1
                                  , dot p3 <| deg30*2
                                  , dot p4 <| deg30*3
                                  , dot p5 <| deg30*4
                                  , dot p6 <| deg30*5
                                  ]]
       in disp <~ ticks ~ sig 0 ~ sig 100 ~ sig 200 ~ sig 300 ~ sig 400 ~ sig 500

実行画面

上記のソースを変更した上で実行したい場合はこちらからどうぞ。ブラウザ内で実行できます。フル画面表示はこちらから。fps_値を増やすとなめらかになりますが、ブラウザ負荷がたぶん上がります。

気づいたこと・解説

  • Signal.delay関数で元のticksのSignalを「時間的にずらしたシグナル」を作れる(sig dではついでにラジアン単位の角度にしている)
  • おそらくJavaScriptのonTimerイベントの精度の関係で微妙な誤差が出るのですが、なんか有機生命みたいなゆらぎが出てておもろい。
    • 角度でずらせば、正確にずらせるんですが、FRPの面白さを堪能するために、時間でずらしてます。
    • hatena blog記事中のインラインフレーム中で実行させてると、また遅延が違うので動きが違う。味わい深い。
    • 補助線を引いてみると以下のとおり。

関連エントリ


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

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

プログラミングHaskell

プログラミングHaskell