読者です 読者をやめる 読者になる 読者になる

taketoncheir.log

Like the Decatoncheir by Poseidon Industrial, This blog is Yet Another Storage for My Long Term Memories.

Haxeで.hxmlをコンパイル、.jsを吐き出す

最近Haxeを触ってます。
基本ではありますが、備忘録として記事を残しておきます。

hxmlを用意

こんなディレクトリだったとして

---- haxe
   |       |
   |       --  hello_haxe.hxml
   |       |
   |       --  hello
   |                |
   |                --  main
   |                         |
   |                         --  Main.hx
   |
   -- html
          |
          --  hello

html/hello以下に、hello.jsを生成したいとしましょう。

その場合、hello_haxe.hxmlはこうなる。

// hello_haxe.hxml
-js ../html/hello/hello.js
-main hello.main.Main
  • jsで出力先を、-mainでコンパイル時のMain.hxを指定している。

このhello_haxe.hxmlに対して、

$ haxe hello_haxe.hxml

とすると、html/hello/hello.jsが出来てる!

え?haxeなんてコマンドないって?
ではインストールしましょう。
Windows, OSXの方はこちら
Ubuntuだとapt-get経由でインストールするとversionが2.04で古かった。その場合は、このthe NME installation scriptを使うと良い。

Data.AesonのJSONパースをデバッグする

Haskell製のWebフレームワーク、ScottyとAngularJSを使ってアプリを作っています。
Magpie

JSONのパース周りで割とはまったので、そのデバッグ過程をメモしておきます。

Model周辺のみを抜き出したテストプロジェクトを用意したので参照してください。

デバッグ環境を整える

cabal環境をいじると依存性地獄に落とされますのでsandbox環境を用意するために、cabal-devを使います。(cabal-devはソースコードからビルドします)

$ cabal-dev install --only-dependencies
$ cabal-dev install

これでData.AesonやDatabase.Persistentなどの必要なライブラリが./dist/binにインストールされます。

以下のコマンドで、ghciでデバッグします。

$ cabal-dev ghci

Userを作成

Model.hsにて以下のデータ型を定義しています。Userと定義した部分にjsonとつけることにより、どのようにJSONとパースするのかの変換式(FromJSON/ToJSON)をTemplate Haskellが自動生成してくれます(参考)。

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persist|

User json
    name Text
    email Text
    editor Editor
    UniqueUser email
    deriving Show
|]

data Editor
    = VIM | EMACS
    deriving (Show, Read, Eq, Enum)

derivePersistField "Editor"

まずはモジュールを読み込みます。

Prelude Main> :m + Data.Aeson Model

StringをByteStringリテラルとして扱うために、言語拡張をセットします。

Prelude Model Data.Aeson Main> :set -XOverloadedStrings

準備ができました。ユーザーmeを作ります。

Prelude Model Data.Aeson Main> let me = User "taketon_" "take.ton@hoge.com" VIM
Prelude Model Data.Aeson Main> me
User {userName = "taketon_", userEmail = "take.ton@hoge.com", userEditor = VIM}

Userをdecodeしたりencodeしたり

User型のmeをJSONにパースしてみましょう。それにはData.Aesonモジュールのencode関数を使います。

Prelude Model Data.Aeson Main> encode me
Chunk "{\"name\":\"taketon_\",\"editor\":{\"editor\":\"VIM\"},\"email\":\"take.ton@hoge.com\"}" Empty

VIM型が{"editor" : "VIM"}にエンコードされているのは、ToJSONを以下のように定義しているからです。

instance A.ToJSON Editor where
    toJSON VIM = A.object [ "editor" A..= ("VIM" :: String) ]
    toJSON EMACS = A.object [ "editor" A..= ("EMACS" :: String) ]

同様に、JSONをパースしてEditor型に変換する際は以下のFromJSONが使われます。

instance A.FromJSON Editor where
    parseJSON (A.Object v) = read <$> (v A..: "editor")
    parseJSON _            = mzero

JSONをUser型に戻してみましょう。Data.Aesonモジュールのdecode関数を使います。

Prelude Model Data.Aeson Main> decode "{\"name\":\"taketon_\",\"editor\":{\"editor\":\"VIM\"},\"email\":\"take.ton@hoge.com\"}"

<interactive>:6:8:
    No instance for (Data.String.IsString
                       Data.ByteString.Lazy.Internal.ByteString)

エラーです。Data.ByteString.Lazy.Char8を追加してもう一度。

Prelude Model Data.Aeson Main> :m + Data.ByteString.Lazy.Char8
Prelude Model Data.Aeson Data.ByteString.Lazy.Char8 Main> decode "{\"name\":\"taketon_\",\"editor\":{\"editor\":\"VIM\"},\"email\":\"take.ton@hoge.com\"}" :: Maybe User
Just (User {userName = "taketon_", userEmail = "take.ton@hoge.com", userEditor = VIM})

パースした結果がMaybe User型であることを注釈してあげます。
今度はちゃんとUser型が手に入りました。
ここでパースが失敗すると、Maybe User型はNothingになります。その場合は、FromJSON/ToJSONの定義を見直す必要があります。

こうした方法でパースを試すことによって、どのようなJSON型になるのか、復元は可能なのか検証できます。

riak1.3.0をOSX10.6にインストール

以下の手順でインストール出来ました。
ちなみに自環境はOSX 10.6.8です。
いい加減アップグレードしないと。。

Erlang R15B01をインストール

Installing Erlangを参考にインストール。当初Homebrew経由でインストールしていたErlang/OTPはR16でriak-1.3.0がmake出来なかったので、R15B01をインストールし直した。

上記サイトのうち、kerlを使う方法でインストール。そのままのコマンドで終了。

riak 1.3.0をインストール

Basho.comを参考にインストール。
R15B01が入っていれば問題ない。@kuenishiさんの記事などを見るとR15B02でもいけるっぽい。インストールの記事はつかったので参考にさせてもらいました。

riak、起動

$ make devrel DEVNODES=4

でノードを4つ作成しました。(※version 1.2.1の記事ではDEVNODESの指定はしていない。また、本番環境ではノード数は5つ以上が良いそう

ディレクトリが出来たので、いそいそと

$ dev1/bin/riak start

として、dev1..4まで起動した!と思って

$ ps aux | grep beam

とすると、あれ、みんなお亡くなりになっている。。。

dev1/log/error.logを見ると、最大fileopen数を超えてしまったようです。
maxfilesのリミットを変えようとちょっと試したのですが、結局OS再起動でmaxfilesを変更せず
ulimit -n 256で動きました。@kuenishiさんにご指摘頂き、ulimit -n 4096は必須とのことです。

(※OSXでmaxfilesを変えるには2つの経路があるようで(launchctlとulimit)、詳しくは分からないのですがriakはulimit -nの値を参照しているようです。それぞれ、$ launchctl limit / $ulimit -aで確認できます。maxfilesの変更方法はここにかいてあるが、とりあえず$ ulimit -n 4096でいいはず)

ひとまず動いたので、to be continued...

Angularで異なるdivに同一名controllerを登録してもそれらは同じcontrollerを指さない

fiddleはこちら

Angularjsで$scopeの状態を見るには、Chrome ExtentionのBatarangを入れるのがいい。
それと、consoleでangular.element($0).scope();

jsfiddle自身のサイトではangularが見えない。
上記fiddleのURL末尾に/showを付けてやるとホストされた状態で見える。参考

http://jsfiddle.net/taketon_/x6G4F/show

これでBatarangを通してscopeの状態も見えるし、angularからelementを取ることもできる。

上記fiddleでは確かにdivそれぞれに対して異なるidのscopeが割り当てられているのが見て取れる。

まぁコントローラを分けろというのはわかる。
Storage的なサービスを作るか、persistent.jsあたり使ってglobalな状態を持たせるのかな。

あと、なぜかpulldownを触ると同一div内のng-repeatリストが消え去ってしまう。
なかなか前に進まないなぁ。

Scotty、起動

軽くWebアプリ作ろうと思い、Scottyに触れてみることにしました。

Scotty (github)

使用環境

  • MacOS 10.6.8
  • Haskell-platform2012.4.0.0
  • Scotty 0.4.6
  • cabal-dev 0.9.1, cabal 1.14.0

起動まで

とりあえずソースコードをclone。

$ git clone git://github.com/xich/scotty.git

(追記:Cabal-1.18のSandbox機能により、cabal-devはもう使わなくてもいいかもしれません)
まずCabalリポジトリ本体を壊したくないので、.cabalファイルがあるディレクトリでcabal-devコマンドを使用することで依存パッケージをsandboxで配置(cabal-devはcabal-devソース本体をチェックアウトしてcabal installすれば大抵パスが通って使えるはず。cabalはupdateしろと警告されるがしないほうがいい。cabal-dev ghciが使えなくなるのでデバッグ作業ができなくなる)。

$ cd scotty
$ cabal-dev install --only-dependencies

これで、依存パッケージが./cabal-dev/lib以下に入った。
その構成は、./cabal-dev/packages-7.4.1.conf以下で示される。

Scotty (github)にあるように素直に

$ runghc examples/basic.hs

とすると、wai-extraがないよと怒られる。

cabal-dev/libの中にいることを教えてあげる。

$ runghc -package-conf=cabal-dev/packages-7.4.1.conf examples/basic.hs

すると(port 3000)で立ち上がるので、http://localhost:3000/にアクセス。
foobarって見えたヽ(*´∀`)ノ キャッホーイ!!

実行ファイルを作りたければ、

$ ghc -package-conf=cabal-dev/packages-7.4.1.conf examples/basic.hs

でexamples以下にbasicが出来ている。

QuickCheckのコード読んでみた

QuickCheckでtestデータがgenerateされる仕組み

実際に定義したデータ型に対してデータを生成する方法はこちら
ここではQuickCheckのソースコードについて記します。
実際に読み取った順に書いているので分かりにくいです、すいませんm(__)m

登場人物

  • class Testable : ユーザが定義した、Boolを返すプロパティ
  • quickCheck : ユーザが定義したTestableを受け取り、試行ごとにランダム値を生成しながらStateを引き回していくテスト管理部分
  • newtype Gen a : StdGenとサイズ上限値を取って生成されたデータを返す。サイズ上限値は例えばリスト型のデータ生成時に生成するlengthの上限値xとなり、lengthは1~xの中からランダムな値が選ばれる。
  • class Arbitrary a : テストデータ生成部分
  • type Property = Gen Prop : プロパティがテストされた結果としてのPropをGenで包んだもの

お題

以下のコードにおいて、quickCheckによりプロパティ部分にどのような値が与えられるのか?

quickCheck ¥xs -> reverse . reverse xs == xs

また、QuickCheckではテストデータ生成は検証開始時点では生成する範囲が狭く、テストが進むとだんだん範囲が広くなっていくふるまいをとります。このふるまいが決まるのはどこなのか?

ghciデバッグ

上記のxsに渡される型は[a]になります。
これを確かめるには、ghciでデバッグするのがよいでしょう。
QuickCheckプロジェクト直下、QuickCheck.cabalが存在するディレクトリでcabal-dev ghciを実行し、

Prelude > :i ¥xs -> (reverse . reverse) xs == xs

と入力すると、以下のように型を得ます。

¥xs -> (reverse . reverse) xs == xs :: Eq a => [a] -> Bool

つまり、quickCheckはランダムな長さ(サイズ)のリスト[a]を生成し、reverse.reverseに渡していきます。

quickCheck

quickCheckはTest.QuickCheck.Testモジュールに定義されています。
最終的にquickCheckWithResult :: Testable prop => Args -> prop -> IO Resultに処理が渡り、test MkState (unGen (property p))のpにユーザーが定義したプロパティ(上のラムダ式)が入ります。

test関数はテストのMkStateに記された実行状況(成功した試行数等)を見て、呼び出す関数を切り替えます。通常はrunATest関数が呼ばれます。

runATestでは、MkStateで定義されたcomputeSize'関数が成功した試行数とDiscardされた試行数を元に次の試行で生成するサイズを決定します(注:このcomputeSize'関数で試行毎に渡されるサイズが決定されています。ここでお題に書いた振る舞いが決まります。これについては後述します。)
このサイズとMkStateに格納されたrandomSeedを(StdGen -> Int -> Prop)に入力します。それが、runATest関数中の(f rnd1 size)部分です。それに対してunPropを呼び、結果を取り出します。
ここで、fは上のunGen (property p)の結果が入ります。
propertyというのは型クラスTestableのメソッドです。

型クラスTestable

Test.QuickCheck.Propertyモジュールに記載されています。
Testable型クラスは、ユーザが定義したプロパティ(a -> Bool)からProperty型を生成するpropertyメソッドを提供します。
その実装はこうです。

instance (Arbitrary a, Show a, Testable prop) => Testable (a -> prop) where
  property f = forAllShrink arbitrary shrink f

forallShrinkの実装は以下です。

-- | Like 'forAll', but tries to shrink the argument for failing test cases.
forAllShrink :: (Show a, Testable prop)
             => Gen a -> (a -> [a]) -> (a -> prop) -> Property
forAllShrink gen shrinker pf =
  gen >>= \x ->
    shrinking shrinker x $ \x' ->
      printTestCase (show x') (pf x')

gen >>= \x部分はGen aのMonad実装が該当します。
Gen aのMonad実装は以下です。

instance Monad Gen where
  return x =
    MkGen (\_ _ -> x)

  MkGen m >>= k =
    MkGen (\r n ->
      let (r1,r2)  = split r
          MkGen m' = k (m r1 n)
       in m' r2 n
    )

ここではGen aのGenを剥いて、aを取り出して¥xに渡したと考えればよい。つまり、forAllShrinkに与えられた第一引数のarbitrary関数の結果がGen aで、そのaが¥xに入ることになります。arbitrary関数はテストデータを産み出す関数ですので、このaは生成された値をさします。そして、shrinking関数の中でこのaがforAllShrinkの¥x'に入り、ユーザが定義したプロパティpfにx'が渡されます。これでやっと、プロパティと生成された値が繋がりました!

Arbitrary型クラス

実際に値を生み出す関数、arbitraryはTest.QuickCheck.ArbitraryモジュールのArbitrary型クラスで定義されています。

-- | Random generation and shrinking of values.
class Arbitrary a where
  -- | A generator for values of the given type.
  arbitrary :: Gen a
  arbitrary = error "no default generator"

今回生成するテストデータの型は[a]なので、そのinstanceはこうです。

instance Arbitrary a => Arbitrary [a] where
  arbitrary = sized $ \n ->
    do k <- choose (0,n)
       sequence [ arbitrary | _ <- [1..k] ]

じゃchooseってなんだと。

-- | Generates a random element in the given inclusive range.
choose :: Random a => (a,a) -> Gen a
choose rng = MkGen (\r _ -> let (x,_) = randomR rng r in x)

と、タプルで範囲(rng)を取ってその範囲内の値ひとつをGenで包んで返します。
つまり、property関数の中で呼ばれるarbitrary関数は、choose関数を呼んで0からサイズ上限値の範囲からランダムな長さのリストを返す、という流れになります。

Gen型クラス

Gen型クラスはStdGenとサイズ上限値を取って生成されたデータを返す振る舞いを表します。
その生成は、unGen :: StdGen -> Int -> a関数によって行われます。
このunGenが呼ばれるのは、quickCheckWithResult関数の中のtest関数です。

テストデータのサイズが広くなっていく振る舞い

QuickCheckでは、テストデータ生成は検証開始時点では生成する範囲が狭く、テストが進むとだんだん範囲が広くなっていくふるまいをとります(リストであれば、[]から始まり、lengthが長くなっていく)。
この振る舞いが決まるのが、runATest内で定義されているcomputeSize'関数です。
詳しく読んでいないのですが、

-- e.g. with maxSuccess = 250, maxSize = 100, goes like this:
-- 0, 1, 2, ..., 99, 0, 1, 2, ..., 99, 0, 2, 4, ..., 98.

とあるので、numSuccessTestsとnumRecentlyDiscardedTestsを受け取って、上のようにサイズが大きくなっていく振る舞いを取る、と。

おわり

QuickCheck、小さくていいのですが頭がこんがらがりますね...
やってることはarbitraryで生成した値を受渡しているだけです。
使う側もarbitraryだけ定義すればいいのがわかります。
QuickCheckのコードを読む上で嬉しいのは、依存ライブラリがbaseくらいであることです!dep-hellと無縁!
QuickCheck、Haskell初心者の勉強におすすめです。

QuickCheckでデータ型に対するテストを書く

QuickCheckで自分で定義したデータ型のテストを書く

QuickCheckです。
QuickCheckはIntやStringについてデータを生成はしてくれますが、自分で定義したデータ型についてはそのままでは生成してくれません。

データ型に対してArbitraryのinstanceを定義すればよい

例えば、以下のようなデータ型を定義したとする。

data Person = Person
	{ name :: Text
	, age :: Int
	, sex :: Sex
	}
  deriving (Show, Eq)

data Sex = Male | Female
  deriving (Show, Eq)

Personというデータ型について、name, age, sexというフィールドがあると。
sexはSex型で、値としてMaleかFemaleがありえます。

QuickCheckでこのPersonの値をランダムに生成したい場合、以下のようになります。

import Control.Monad
import Control.Applicative
import Test.QuickCheck
import qualified Data.Text as DT

instance Arbitrary Person where
    arbitrary = return Person 
        `ap` (DT.pack <$> arbitrary)
        `ap` arbitrary
        `ap` arbitrary

instance Arbitrary Sex where
    arbitrary = elements [Male, Female]

Personの各フィールドに対応したarbitraryが呼ばれます。
一つ目のarbitraryでは、instance Arbitrary Stringでの定義が、二つ目のarbitraryでは、instance Arbitrary Intでの定義が呼ばれます。
三つ目はinstance Arbitrary Sexでの定義が呼ばれます。
ここでは、MaleかFemaleを要素としてどちらかを生成するelements関数を用いています。

以上で実行すると、Personのフィールドにランダムな値が入ったデータが返されると。

プロパティに与える際にデータをフィルタリングしたい場合は、forAll関数等を用いて条件をセットしてやります。

-- Maleだけ通す
genMales :: Gen [Person]
genMales = (filter (\x -> personSex x == Male)) <$> arbitrary

-- Maleばかり与えられたら、Femaleの合計は0のはず
prop_calcNumPerAge_allMales :: [Person] -> Property
prop_calcNumPerAge_allMales xs =
    forAll genMales $ \xs ->
    collect (length xs) $
        (sumFemales $ calculateNumPerAge xs) == 0
  where
    sumFemales :: AllNumPerAge -> Int
    sumFemales x = (over0Female x)
                + (over20Female x)
                + (over40Female x)
                + (over60Female x)

以上のコードはこちらにあります。
QuickCheck、簡単ですねぇ。