Groovy 2.4のgroovyshでは、defが効くようになった。
https://jira.codehaus.org/browse/GROOVY-6623
で対処されました。
% groovysh Groovy Shell (2.4.0-beta-1, JVM: 1.8.0_11) Type ':help' or ':h' for help. ------------------------------------------------------------------------------------------------------------------------------- groovy:000> def x = 3 ===> 3 groovy:000> x ===> 3 groovy:000> :set Preferences: interpreterMode=true groovy:000> x.class ===> class java.lang.Integer groovy:000> String x = 3 ===> 3 groovy:000> x.class ===> class java.lang.String groovy:000>
こうです。
(2014/07/31追記)
もちろん、defに限らず
groovy:000> String t = "abc" 000> String t = "abc" ===> abc groovy:000> t 000> t ===> abc
のように型を指定してもOKです。ローカル変数のスコープが、1行単位ではなく、セッションを通じて有効になる、という話。今までは一行ごとだった、つまり
groovy:000> String s="a"; s 000> String s="a"; s ===> a
はエラーにならなかった。
(2014/07/31追記終り)
前の動作に戻すには、
groovy:000> :set interpreterMode false groovy:000> def y = 3 ===> 3 groovy:000> y Unknown property: y
こうします。なんで今まで…、っていう話はあるんでしょうが、行ごとにnew GroovyShell()してたから、という実装の都合で、まあ、あれだ。
また、別件ですが、
https://jira.codehaus.org/browse/GROOVY-6754
によれば、
groovyshに、groovyコマンドと同様に-eオプションが指定でき、groovysh起動のタイミングで固有の初期設定コードが実行できるようになりました。
elmでやってみるシリーズ8: 赤いドットが回っているかのように見えるけど実は直進運動な錯視
Elmでやってみるシリーズ8: 赤いドットが回っているかのように見えるけど実は直進運動な錯視
少し前に、「このくるくる回る白いドット、実は真っ直ぐ往復してるだけなんだぜ」という記事がありましたが、これをElmで再現してみようというのが今回のネタ。
(追記 id:waman さんが数学解析をされています→回転しているようにみえる白いドットは単振動している - 倭算数理研究所 。このネタ自体もwamanさんのTwitterで知ったんですけどね。)
以下は画面キャプチャ(静止画)。
コードは以下のとおり。
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
気づいたこと・解説
- Signal.delay関数で元のticksのSignalを「時間的にずらしたシグナル」を作れる(sig dではついでにラジアン単位の角度にしている)
- おそらくJavaScriptのonTimerイベントの精度の関係で微妙な誤差が出るのですが、なんか有機生命みたいなゆらぎが出てておもろい。
- 角度でずらせば、正確にずらせるんですが、FRPの面白さを堪能するために、時間でずらしてます。
- hatena blog記事中のインラインフレーム中で実行させてると、また遅延が違うので動きが違う。味わい深い。
- 補助線を引いてみると以下のとおり。
- CanvasのAPIなんかまったく知らなくても、Elmライブラリのリファレンスだけ見れば使えるようになるのは偉大。
- iPhoneだと動かない。なんでや。
関連エントリ
「Elmでやってみる」シリーズのまとめエントリ - uehaj's blog
- 作者: Miran Lipovača,田中英行,村主崇行
- 出版社/メーカー: オーム社
- 発売日: 2012/05/23
- メディア: 単行本(ソフトカバー)
- 購入: 25人 クリック: 580回
- この商品を含むブログ (67件) を見る
- 作者: Miran Lipovaca
- 出版社/メーカー: オーム社
- 発売日: 2012/09/21
- メディア: Kindle版
- 購入: 4人 クリック: 9回
- この商品を含むブログを見る
- 作者: Graham Hutton,山本和彦
- 出版社/メーカー: オーム社
- 発売日: 2009/11/11
- メディア: 単行本(ソフトカバー)
- 購入: 14人 クリック: 503回
- この商品を含むブログ (117件) を見る
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との連携に興味があったので、今回試してみました。
やったことは以下のとおり。
- elm-getでelm-htmlをインストール
- elm-get install evancz/elm-html 0.1.2
- HtmlTest.elmを作る(後述)
- 以下のように--only-jsオプションを付けてelmコードをコンパイル。なお、--only-jsを使用する場合、呼び出すためにモジュール名が必要なので冒頭でmodule宣言が必要になる。
- elm --make --only-js HtmlTest.elm
- Twitter Bootstrapを使用するindex.htmlをこちらの「Embed in HTML」を見て適当につくる。index.htmlの内容はこちらを表示してビューソースしてみてください。
- 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
気づいたこと
- 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
- 作者: Miran Lipovača,田中英行,村主崇行
- 出版社/メーカー: オーム社
- 発売日: 2012/05/23
- メディア: 単行本(ソフトカバー)
- 購入: 25人 クリック: 580回
- この商品を含むブログ (67件) を見る
- 作者: Miran Lipovaca
- 出版社/メーカー: オーム社
- 発売日: 2012/09/21
- メディア: Kindle版
- 購入: 4人 クリック: 9回
- この商品を含むブログを見る
elmでやってみるシリーズ6: 今日の天気予報を表示する(フォーム入力とWebAPI呼び出しとJSONで)
Elmでやってみるシリーズ6: 今日の天気予報を表示する。
今回は、以下の手順でお天気情報を表示します。
- Graphics.Input.dropDownとGraphics.Input.Field.fieldで、場所情報を入力させる
- 上記の2つはInputを共有させる
- 変更があったらHttp.sendGetでhttp://openweathermap.org/の各地天気を得るWeb APIを呼び出す
- 得られた結果をJSONとしてパースして、天気情報に相当する部分を取り出して表示する。
コードは以下のとおり。
import Http import Graphics.Input import Graphics.Input.Field as Field import Json import Dict -- フィールドの入力値を抽象化したもの。 inp : Graphics.Input.Input Field.Content inp = Graphics.Input.input Field.noContent -- 文字列をフィールド入力値に変換(文字方向、テキスト選択領域などは使わないので適当に設定) cont : String -> Field.Content cont str = Field.Content str (Field.Selection 0 0 Field.Forward) -- 都市名を選択するセレクトフィールド。入力内容は、inpに伝播する。 citySelect : Element citySelect = plainText "Select City:" `beside` Graphics.Input.dropDown inp.handle [ ("" , cont "") , ("Yokohama", cont "Yokohama,JP" ) , ("Tokyo" , cont "Tokyo,JP") , ("Okinawa" , cont "Okinawa,JP") ] -- 都市名を自由入力する入力フィールド。入力内容は、inpに伝播する。 cityInput : Field.Content -> Element cityInput fldCont = plainText "or Input City:" `beside` Field.field Field.defaultStyle inp.handle id "Please input city name" fldCont -- 天気予報Web APIのURL base : String base = "http://api.openweathermap.org/data/2.5/weather?q=" -- 天気予報Web APIの呼び出し結果であるJSON文字列から、結果部分のみを抜き出す getWeather : String -> String -> String getWeather city body = case (Json.fromString body) of Just (Json.Object dict) -> case (Dict.get "weather" dict) of Just (Json.Array [Json.Object d]) -> case (Dict.get "description" d) of Just (Json.String description) -> "\n\nTenki of "++city++" is "++ description _ -> "nothing" _ -> "nothing" -- 画面下部の結果表示部分 resultPart : String -> Http.Response String -> Element resultPart city resp = if city=="" then plainText "Please select or input city" else case resp of Http.Success body -> plainText <| getWeather city body Http.Waiting -> image 16 16 "waiting.gif" Http.Failure _ _ -> asText resp -- セレクトフィールドcitySelectもしくは入力フィールドcityInputのいずれかに入力された都市名のシグナル(inp.signal)に依存してHttp.sendGetを発行させ、その結果が画面下部に表示されるようにする(そういうシグナル間の依存関係を作る)。 main : Signal Element main = let display city resp = flow down [citySelect, cityInput city, resultPart city.string resp] toUrl city = base++city.string in display <~ inp.signal ~ (Http.sendGet (toUrl <~ inp.signal))
実行画面(操作可能)
上記のソースを変更した上で実行したい場合はこちらからどうぞ。ブラウザ内で実行できます。
気づいたこと
- Http.sendGetの出力はいいとして、入力となるURLもSignalである。こうなってる理由はよくわからないが、
- まずピュアな値をシグナルに変換するのはSignal.constantでも可能で簡単だから。Signalに対応させておけばピュア値もSignal値にも両対応になる、ということがあるかもしれない。Signalであれば、いちいちliftしなくてもInputの入力を繋げることができ、今回のような用途には向いている。
- 次に、たぶんこれが正解だと思うのだが、ConcurrentFRP特有の事情がある。Elmの処理の実行は、変更伝搬を元にしている。Input Signal*1が変化したら、それに依存した依存Signal値を更新していく、という処理の流れをとることである。入力がSignalではなく、かつInput Signalでは無いSignalは、発火する(実行してそのSignalノードの値を更新する)タイミングがない。
- signal引数が変化しないなら呼び出し自体を省略する、といった最適化の事情に関連があるかもしれないかと一瞬思ったが、haskellのIOから類推すると、返り値さえSignalであれば省略されないということが保証できそうなので却下。
- Signalの取り回しはまだ私には難しい。結果的にはリアクティブコードがmainに集約できている。
- 標準のJSON処理はあまりにも面倒。ダイナミックにレコード構文として扱えるような機能はないものか。
- Graphics.Input.Fieldは、右から左に書く言語のための方向指定とか、セレクションの設定とかが必須で難しすぎる。単に文字列が得られるような簡易インターフェースも欲しい。
- share elmは日本語が表示できない。try-elmなら問題ないんだけど保存とフルスクリーン表示ができないためshare-elmを使用。
- 「型さえ合えばまず動く」という感覚がわかってきた。でも型を合せるのが大変。その意味では、前にトップレベル定義の型アノテーションはかならずしも必要ない、とか書いたけど自明ではないプログラムや、プログラムを機能拡張する場合などを考えると実質的には必須になるという気がしてきた。
- case式でマッチしないものがあるとJSレベルでエラーとなる。「マッチしない可能性があるcase式」はエラーではないにせよ、警告してくれないものか。
関連エントリ
「Elmでやってみる」シリーズのまとめエントリ - uehaj's blog
- 作者: Miran Lipovača,田中英行,村主崇行
- 出版社/メーカー: オーム社
- 発売日: 2012/05/23
- メディア: 単行本(ソフトカバー)
- 購入: 25人 クリック: 580回
- この商品を含むブログ (67件) を見る
- 作者: Miran Lipovaca
- 出版社/メーカー: オーム社
- 発売日: 2012/09/21
- メディア: Kindle版
- 購入: 4人 クリック: 9回
- この商品を含むブログを見る
*1:マウスやキー入力やタイマーイベントなど、Elmプログラムの外にあるものを直接の入力元とするSignal。
elmでやってみるシリーズ5: 逆ポーランド電卓
いわゆる一つの逆ポーランド記法(Reverse Polish Notation, RPN)電卓です。入力フォームをSignalに結びつけて入力として使う例になります。私は本物のRPN電卓を使ったことがないので本物とはたぶん動作が違います。
import Graphics.Input (input, button) -- 電卓のボタン操作の種別 data Keys = Number Int | Add | Sub | Mul | Div | Clear | Enter -- GUIからの電卓に対する操作入力列を表現するシグナル。初期値はClear。 keys = input Clear -- 画面を作る。 calculator (n, xs) = let btn = button keys.handle btnN n = btn (Number n) (show n) in flow down [ flow right [ btnN 7, btnN 8, btnN 9, btn Add "+" ] , flow right [ btnN 4, btnN 5, btnN 6, btn Sub "-" ] , flow right [ btnN 1, btnN 2, btnN 3, btn Mul "*" ] , flow right [ btnN 0, btn Clear "Clear", btn Enter "Enter", btn Div "/" ] , if (n==0) then (asText xs) else (asText n) ] -- 有限状態マシン(状態=入力エリアへの入力中の値とスタックのタプル)としての電卓のモデル化。 calc : Keys -> (Int, [Int]) -> (Int, [Int]) calc it (num, xs) = case it of Number n -> (num*10+n, xs) Enter -> (0, num::xs) Clear -> (0,[]) _ -> if (length xs < 2) then (0, xs) else let top = head xs second = head (tail xs) rest = tail (tail xs) in case it of Add -> (0, (second+top) :: rest) Sub -> (0, (second-top) :: rest) Mul -> (0, (second*top) :: rest) Div -> (0, (second `div` top) :: rest) -- foldpで、入力操作列シグナルに対して、「1回前の電卓状態に、入力操作を適用し、次状態を生成する」という関数をリフト適用する。 main=lift calculator (foldp calc (0,[]) keys.signal)
実行画面(操作可能)
上記のソースを変更した上で実行したい場合はこちらからどうぞ。ブラウザ内で実行できます。
気づいたこと
- コロン(:)、ダブルコロン(::)はHaskellのそれとちょうど意味が逆になっている。既存のhaskell編集モードを流用したい場合、不便かも。
asパターンはない。asパターンは@ではなく、... as someのように記述。
NG: let (x@{a,b}) = {a=1, b=2} in x OK: let ({a,b} as x) = {a=1, b=2} in x
case式でリストをパターンマッチで分解できない。具体的には「case (x:xs) of ...」などができず、(x:xs)の部分は単一の変数名しか指定できない。(x:xs)を指定してもエラーにならないので、Elmのバグではなかろうか。(本件は記述が変なのと詳細を覚えてないので削除)- Inputのhandleは、複数のGUI部品からの入力を集約したSignalを作成するための間接参照点である。(という理解で正しいか?)
- Elmに例外ってないみたいだが、たとえば(head [])するとJavaScriptでの実行時例外になって、プログラムは再開できなくなる(もちろんページ全体をリロードすれば操作は再開できるが)。そうならないようにチェックすれば良いのだが、そういうものか?
- トップレベル定義の型アノテーションを型推論させて表示するには、Elmコマンドの-pもしくは--print-typesでできる。しかしアノテーションはあまり必要な気がしない(逆にアノテーションが本体関数の変更に追随しないことで良くエラーになる)が、ドキュメント情報としてみると便利であろう。
- foldlかfoldrで逆ポーランド電卓を作るのは、すごいH本の例にあったが、上のelmのはfoldpで同型。一般化すると、一連の入力シーケンスにともなって状態を変化させる処理は、適切な粒度のSignal+foldpの抽象化に対応するということか。ファイルからの入力文字ストリームはSignalにできそう。書き込みはどうか?ソケット通信やDBはどうか。
関連エントリ
「Elmでやってみる」シリーズのまとめエントリ - uehaj's blog
- 作者: Miran Lipovača,田中英行,村主崇行
- 出版社/メーカー: オーム社
- 発売日: 2012/05/23
- メディア: 単行本(ソフトカバー)
- 購入: 25人 クリック: 580回
- この商品を含むブログ (67件) を見る
- 作者: Miran Lipovaca
- 出版社/メーカー: オーム社
- 発売日: 2012/09/21
- メディア: Kindle版
- 購入: 4人 クリック: 9回
- この商品を含むブログを見る
追記: Elm 0.14対応
import Graphics.Input (..) import Signal import Signal(Channel) import Graphics.Element (..) import Text(..) import List(..) type Keys = Number Int | Add | Sub | Mul | Div | Clear | Enter keys : Channel Keys keys = Signal.channel Clear calculator : (Int, List Int) -> Element calculator (n, xs) = let btn x = button (Signal.send keys x) btnN n = btn (Number n) (toString n) in flow down [ flow right [ btnN 7, btnN 8, btnN 9, btn Add "+" ] , flow right [ btnN 4, btnN 5, btnN 6, btn Sub "-" ] , flow right [ btnN 1, btnN 2, btnN 3, btn Mul "*" ] , flow right [ btnN 0, btn Clear "Clear", btn Enter "Enter", btn Div "/" ] , if (n==0) then (asText xs) else (asText n) ] calc : Keys -> (Int, List Int) -> (Int, List Int) calc it (num, xs) = case it of Number n -> (num*10+n, xs) Enter -> (0, num::xs) Clear -> (0,[]) _ -> if (length xs < 2) then (0, xs) else let top = head xs second = head (tail xs) rest = tail (tail xs) in case it of Add -> (0, (second+top) :: rest) Sub -> (0, (second-top) :: rest) Mul -> (0, (second*top) :: rest) Div -> (0, (second // top) :: rest) main=Signal.map calculator (Signal.foldp calc (0,[]) (Signal.subscribe keys))
elmでやってみるシリーズ4: 画像ギャラリー
elmでやってみるシリーズ4: 画像ギャラリー。
論文「"Concurrent FRP for Functional GUIs"」にものってた、画像ギャラリー的なもの。キーボードの矢印キーの左右で画像を選択できます。
import Keyboard -- WikipediaのURL(各画像URLの共通部分) base = "http://upload.wikimedia.org/wikipedia/commons/" -- 画像情報(タイトルとURL)をレコード型で表現する picts = [{title="Het melkmeisj", url=base++"2/20/Johannes_Vermeer_-_Het_melkmeisje_-_Google_Art_Project.jpg"}, {title="Girl with a Pearl Earring", url=base++"c/ce/Girl_with_a_Pearl_Earring.jpg" }, {title="Womanholdingabalance vermeer", url=base++"7/7e/Womanholdingabalance-vermeer.jpg" }, {title="Le Moulin de la Galette", url=base++"2/21/Pierre-Auguste_Renoir%2C_Le_Moulin_de_la_Galette.jpg"}, {title="Luncheon of the Boating Party", url=base++"e/ea/Auguste_Renoir_-_Luncheon_of_the_Boating_Party_1880-1881.jpg"} ] -- 画像情報のリストからn番目の画像情報を得る。nが負の場合やリスト長を越える場合、リスト長の剰余(mod)を取る。 nthPict n = let x = n `mod` (length picts) in (head (drop x picts)) -- 左右の矢印キーを入力シグナル(+1,-1)を累積的に保存した値をfoldpで作って、「n番目の画像を表示する」にリフト適用。 main = let f n = let {title, url} = nthPict n in flow down [plainText title, fittedImage 200 300 url, plainText "prev<- ->next"] in f <~ foldp (\it acc-> acc+it.x) 0 Keyboard.arrows
ブラウザ内で編集したり実行したい場合はこちらからどうぞ。
気づいたこと
- nthがない(定義はできる。Writing a 2048 clone in elm · Scrambled Eggsにinfixl 9 !の例がある。)
elmでやってみるシリーズ3: xeyesっぽい目が動く
elmでやってみるシリーズ3: xeyesっぽい目が動く。
import Mouse -- 目の輪郭を書く eyeframe x y = move (x,y) <| outlined { defaultLine | width <- 5 } <| oval 40 50 -- 目玉を書く eyeball x y = move (x,y) <| filled black <| circle 5 -- 目(=目玉+目の輪郭)を一個分書く。tは目玉の見ている方向。 eye r x y t = [eyeframe x y, eyeball ((r * (cos t))+x) ((r * (sin t))+y)] -- 目を2つ書く eyes t = collage 200 200 <| (eye 15 -20 0 t)++(eye 15 20 0 t) -- 「目の座標位置を計算する(=「マウス座標の原点から見た角度」をatanで求める)」という純粋関数にマウス座標をリフト適用。シグナル化された目玉の角度が返る。 mouseDirec = let f x y = (atan2 (100-(toFloat y)) ((toFloat x)-100)) in f <~ Mouse.x ~ Mouse.y -- 目を2つ書く、という純粋関数に、シグナル化された目玉の角度(mouseDirec)をリフト適用する。 main = eyes <~ mouseDirec
この簡潔さよ! ブラボー。Elm ブラボー。(さぼってますけどね。)
ブラウザ内で編集したり実行したい場合はこちらからどうぞ。
気づいたこと
- SignalはElmのFRPのキモである。Signalを制するものはElmを制する。
- mainの直下にSignalを使うReactive Codeを固めて、他はなるべくピュアにしたい、と思うじゃない。でもそれはたぶん無理な相談である。いや、やれるところはそうすれば良いと思うけど、「Signalを返す関数」はElmプログラミングにとって中核的であり最重要。それをどう組むべきかという問題からは逃げられない。
- 「なるべくピュアにしたい」という理由は、liftの適用は演算子<~,~があろうともやっぱり可読性に難があるから。今のElmにはセクションがないし、演算子関数をliftすると演算子っぽくなくなるし、flip駆使しても限界がある。もっと言えばSignalはモナドではないし*1、仮にモナドであってもElmにDo記法は無い。で、考えたのですが、ベストプラクティスとしてのコーディングパターンは、上でもしているように、lift一発でできないところは以下のように関数ごとに純粋部とリアクテイブ部に分けることではないかと今のところ思っております*2。なお今のElmはwhere句は使えません。
func = let f x y ... = 純粋コード in f <~ Signal X ~ Singal Y ... -- 必要なSignalを使用するリアクティブコード
- 「{ defaultLine | width <- 5 } 」の記法は、指定したフィールドを更新した新しいレコードを返すというものである。Elmのレコードはすごく強力である。ちなみにレコードの拡張で型クラスを実現することも狙っているらしい。