Now you can visit userscripts.org and any other site that links to Greasemonkey scripts and other flavors of user scripts, click on the link to a *.user.js file and install it in one click.
ということで、userscripts を書くことに。
userstyles.org のサンプルを見て、必要であろうところのみ抽出。JavaScript で head 要素に style タグを追加するだけ。
comb :: CounterOp a b -> (b -> CounterOp a c ) -> CounterOp a c
comb m n = \counter0 -> let (val1, counter1) = m counter0
(val2, counter2) = n val1 counter1
in (val2, counter2)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
comb :: StackOp a b -> (b -> StackOp a c) -> StackOp a c
comb m n = \stack0 ->
let (x1, stack1) = m stack0
(x2, stack2) = n x1 stack1
in (x2, stack2)
「カウンタ操作」の comb 関数は、
comb :: CounterOp a b -> (b -> CounterOp a c ) -> CounterOp a c
comb m n = \counter0 -> let (val1, counter1) = m counter0
(val2, counter2) = n val1 counter1
in (val2, counter2)
ret 関数も同じく「スタック操作」版は、
ret :: b -> StackOp a b
ret x = \stack -> (x, stack)
「カウンタ操作」版は、
ret :: b -> CounterOp a b
ret x = \counter -> (x, counter)
「もう、これは共通部分をまとめるしかない」という雰囲気になった。 ^^;
State s a 型 (状態 操作型) にまとめる
最初に別名から考える。「スタック操作型」と「カウンタ操作型」を総称する名前にしたいので、「状態操作型」という意味で State s a 型という名前にした。(もちろん、Control.Monad.State.Lazy の State を真似て…)
type State s a = s -> (a, s)
先ほど具体的な型が書かれていた StackOp, CounterOp の部分を型変数 s とした。 いきなりこの定義を見たら、抽象的な関数で具体的なイメージがわかない。しかし、これに対応した具体的な関数を連想できるので、State s a 型は 状態を変更する関数を表現したものだと解釈できる。
これに伴い、同じように comb 関数も具体的な型名を型変数で置き換える。
comb :: State s a -> (a -> State s b) -> State s b
comb m n = \s0 -> let (x1, s1) = m s0
(x2, s2) = n x1 s1
in (x2, s2)
ret 関数も同じく、
ret :: a -> State s a
ret x = \s -> (x, s)
ここまでのコード
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
とにかく、これで スタックを操作していたモジュールと、Counter モジュールに記述していた comb, comb_, ret 関数を削除し、上記の State をインポートすればよくなった。
counter.hs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Warning: Fields of `Person' not initialised: age
In the expression: Person {name = "Hanako"}
In the definition of `hanako': hanako = Person {name = "Hanako"}
Ok, modules loaded: Main.
ただし、「人」型の値から「年齢」を得ようとすると、次のような例外が発生する。
*Main> age hanako
*** Exception … : Missing field in record construction Main.age
とする。上記のように undefined を使うと実行時エラーになる可能性があるので Haskell の型チェックの機能を十分に生かせない。 [Haskell-beginners] Default values for Data Types? には Maybe a 型と、データコンストラクタへのラッパー関数を作成して対応する方法が書かれていた。これを真似てみる。
まず「人」型の定義から。年齢を Maybe Int 型とする。
type Age = Maybe Int
data Person = Person { name :: String
, age :: Age
} deriving Show
上記のデータコンストラクタは次のように使う。
tarou = Person "Tarou" (Just 10)
jirou = Person "Jirou" Nothing
しかし、このように書くのは面倒なので、データコンストラクタをラップする関数 person を定義。
person = Person { name = "", age = Nothing }
これを使い、値を更新するふりをして、値を定義する。
hanako = person { name = "Hanako" }
フィールドラベルを書くのすら面倒なら、次のようなラッパー関数を定義。
person' n = Person n Nothing
先ほどよりもシンプルに書けるようになった。
akemi = person' "Akemi"
Maybe a 型を使うメリット
上記のように Maybe a 型を使うといい理由は、Haskell がコンパイル時にエラーで教えてくれるから。
例えば、先ほどと同じように「年齢」を 2 倍にする関数を定義する。年齢がただの Int 型だと思い込んで次のように定義すると、
doubleAge' = (* 2) . age
コンパイル時にエラーが表示される。
No instance for (Num Age)
arising from the literal `2'
at C:\ …
Possible fix: add an instance declaration for (Num Age)
In the second argument of `(*)', namely `2'
In the first argument of `(.)', namely `(* 2)'
In the expression: (* 2) . age
Failed, modules loaded: none.
エラーの内容を読むと、Age 型、つまり、Maybe Int 型に (*) を適用するには、Maybe Int 型が Num クラスのインスタンスでないと無理だということ。実行時に突然エラーが表示されるのと違い、コンパイル時に関数適用の間違いを指摘してくれるので、実行する前にコードを修正できる。
例えば、年齢が未定義の場合、 2 倍しても 0 であるとするなら、次のように定義。
doubleAge (Person n a) = case a of
Nothing -> 0
Just x -> x * 2
Constructor `Person'' does not have the required strict field(s): age'
In the expression: Person' {name' = "Hanako"}
In the definition of `hanako''':
hanako'' = Person' {name' = "Hanako"}
Failed, modules loaded: none.