は? (@_@;) 「結果を返しそこなうかもしれない計算の型」ってどんな型?という疑問が。なぜなら、例えば、自分で定義する 代数的データ型 Person であるなら、それに対応した「人」のイメージを思い浮かべることはできるし、また、真偽値を表わす Bool 型や整数を表わす Int 型も抽象的な概念であるけれど、馴染みがあるので想像の範疇を超えない。それに対して「結果を返しそこなうかもしれない計算」というのは、一体どういうもので、何を具体的に想像すればいいのかわからなかった。
import Data.List
data Person = Person { name, address :: String} deriving Show
ps = [ Person "Tarou" "Tokyo"
, Person "Jirou" "Osaka"
, Person "Saburou" "Nagoya"
]
getAddress n = address . find ((== n) . name)
main = putStrLn $ getAddress "Tarou" ps
これを実行しようとしても、その前にエラーが表示される。
Couldn't match expected type `Person'
against inferred type `Maybe Person'
In the second argument of `(.)', namely `find ((== n) . name)'
In the expression: address . find ((== n) . name)
In the definition of `getAddress':
getAddress n = address . find ((== n) . name)
Failed, modules loaded: none.
今回は address の使い方を間違えているので、Maybe Person 型を扱えるように変更しなければならない。
Maybe a の型に応じた処理を実装
上記の getAddress 関数を以下のように修正。Maybe は Just と Nothing なので、それに応じて処理を分ければよい。
getAddress n ps = case find ((== n) . name) ps of
Just x -> address x
Nothing -> n ++ " ha imasen"
main = do putStrLn $ getAddress "Tarou" ps
putStrLn $ getAddress "Hanako" ps
static Person find(String name) throws Nothing {
for (Person p : Search.persons) {
if (p.getName().equals(name)) {
return p;
}
}
throw new Nothing(name);
}
ついでに、Java の総称型について知識がないので正しい記述なのかわからないが、Haskell の Maybe a 型に相当するクラスを試しに書いてみる。今回は IDE に Netbeans を使っているので、こういうとき IDE が「あれ違うこれ違う」と教えてくれるので、とりあえず動くものが書けるのでありがたい。 ^^
まずは Maybe a 型に対応したクラスを書く。Java ではパターンマッチによって、コンストラクタで作った中身を取り出せないので、Maybe<T> クラスに fromJust メソッドを実装。これにより Just で包んだ中身の値を取り出す。
class Maybe<T> {
protected T a;
T fromJust() {
return this.a;
}
}
class Nothing extends Maybe {
}
class Just<T> extends Maybe {
Just(T a) {
this.a = a;
}
}
Person オブジェクトを検索するメソッドでは、見つかったとき Just クラスのオブジェクトで包み、見つからなかったら Nothing クラスのオブジェクトを返す。
static Maybe<Person> find(String name) {
for (Person p : Search.persons) {
if (p.getName().equals(name)) {
return new Just(p); // 見つかったら Just で包む
}
}
return new Nothing(); // Haskell の Maybe a 型の値 Nothing に相当
}
Collections.sort(persons, new Comparator<Person>() {
public int compare(Person o1, Person o2) {
int c = new Integer(o1.getAge()).compareTo(o2.getAge());
if (c != 0) {
return c;
} else {
return o1.getName().compareTo(o2.getName());
}
}
});
System.out.println(persons); // [Tarou:10, Jiro:20, Hanako:30, Saburou:30]
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
… the side effecting monadic code is mixed in with the pure computation, making it difficult to test without moving entirely into a "black box" IO-based testing model.
*Main> Person "Tarou" 30 < Person "Hanako" 30
False
*Main> Person "Tarou" 30 <= Person "Hanako" 30
True
*Main> Person "Tarou" 30 < Person "Hanako" 10
False
*Main> Person "Tarou" 30 > Person "Hanako" 10
True
Person 型を Ord クラスのインスタンスにしたので、[Person] に sort を適用できるようになった。ps を以下のように定義して、
ps = [ Person "Saburou" 30
, Person "Jiro" 20
, Person "Tarou" 10
, Person "Hanako" 30
]
ソートすると、
*Main> sort ps
[Person {name = "Tarou", age = 10},
Person {name = "Jiro", age = 20},
Person {name = "Saburou", age = 30},
Person {name = "Hanako", age = 30}]
print $ sortBy cmp ps where
cmp x y = let c = comparing age x y
in if c /= EQ then c
else comparing name x y
複数の基準をつなげる
ここで、「人」の属性に「性別」も加える。
data Gender = Male | Female deriving (Show, Eq, Ord)
data Person = Person { name :: String
, age :: Int
, gender :: Gender
} deriving Show
「性別、年齢、名前」の順にソートするなら、
print $ sortBy cmp ps where
cmp x y = let c = comparing gender x y
in if c /= EQ then c
else let c = comparing age x y
in if c /= EQ then c
else comparing name x y
先ほどの [Person] 型のリストの内容を以下のように変更。
ps = [ Person "Youko" 30 Female
, Person "Saburou" 30 Male
, Person "Jiro" 20 Male
, Person "Hanako" 30 Female
, Person "Tarou" 10 Male
]
これを上記でソートした結果、
[Person {name = "Tarou", age = 10, gender = Male},
Person {name = "Jiro", age = 20, gender = Male},
Person {name = "Saburou", age = 30, gender = Male},
Person {name = "Hanako", age = 30, gender = Female},
Person {name = "Youko", age = 30, gender = Female}]
つなげる関数
それにしても、上記のコードは let, if, let, if,… と長い。 (+_+) これを 「Haskell の State モナド (1) - 状態を模倣する」のように、つなぎの部分を部品化しておき、個々のパーツをくっつけることはできないだろうか?
イメージとしては、次のような形。
print $ sortBy cmp ps where
cmp = comparing gender `comb`
comparing age `comb`
comparing name
つなぎの関数名を comb とする。最初の二つの関数 comparing gender, comparing age に注目。この二つを引数に取ることを想像すると、
comb cmp1 cmp2 = ...
comparing gender 関数は、比較のための引数を二つ取るので、
comb cmp1 cmp2 = ... cmp1 x y
cmp1 の結果を変数に束縛する。
comb cmp1 cmp2 = ... let c = cmp1 x y
cmp1 で x, y を引数として取るには、x, y が与えられている必要があるので、無名関数の形で、
comb cmp1 cmp2 = \x y -> let c = cmp1 x y
cmp1 において比較した結果、同値と見なされなかったら、その比較をこの関数の結果とする。
comb cmp1 cmp2 = \x y -> let c = cmp1 x y
in if c /= EQthen c …
そうでなければ、cmp2 で比較する関数を結果とする。
comb cmp1 cmp2 = \x y -> let c = cmp1 x y
in if c /= EQ then c else cmp2 x y
これで先ほどの comb でつなげた関数を実行できるようになる。結果は、
[Person {name = "Tarou", age = 10, gender = Male},
Person {name = "Jiro", age = 20, gender = Male},
Person {name = "Saburou", age = 30, gender = Male},
Person {name = "Hanako", age = 30, gender = Female},
Person {name = "Youko", age = 30, gender = Female}]
Example: http://twitter.com/users/show/12345.json or http://twitter.com/users/show/bob.xml
よって、
http://twitter.com/users/show/ユーザ名.json
によって JSON形式でユーザ情報を取得できる。
ユーザ名を与えると、上記の情報を取得するモジュールを作成。
XML や JSON を取得するには Sources > Fetch Data を利用。
There's more data on the web than just RSS and Atom feeds. This module can access and extract data from XML and JSON (Javascript Object Notation) data sources on the web.
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
pop 関数は、Stack a 型に適用するとタプルが返される。タプルの中身は、最初の要素がスタックから pop により取り出した要素で、2 番目は引数に指定した Stack a 型が、pop により要素が一つ減った状態で返される。実装を見ると、実際は新しくスタックを作って返しているのがわかるけれど、引数と返される値だけを見るならば、この関数によって状態が変更されたと見なすことができる。値を更新できない関数型言語による状態の変更の模倣なので、これを状態の変更だと見なすかどうかは解釈の問題。
sumTwoElem :: Num a => Stack a -> (a, Stack a)
sumTwoElem stack0 = let (x1, stack1) = pop stack0
(x2, stack2) = pop stack1
in (x1+x2, stack2)
スタック s に適用すると結果は、
(9,Stack [3,2,1])
関数の抽象化
さて、ここからが混乱の元であったのと同時に興味深いところ。
Of course, we are Haskell programmers: if there are common patterns or boilerplate in our code, we should search for a way to abstract and capture them in a higher order function.
ここで、先ほどの sumTwoElem 関数に戻り、「4 回 要素を pop し、足し合わせる関数」に変更する。
sumFourElem :: Num a => Stack a -> (a, Stack a)
sumFourElem stack0 = let (x1, stack1) = pop stack0
(x2, stack2) = pop stack1
(x3, stack3) = pop stack2
(x4, stack4) = pop stack3
in (x1+x2+x3+x4, stack4)
抽象化、一般化
関数の抽象化とは、具体的なものを一般的なもので置きかえ、その結果、一般的なものから具体的なものを導き出せる状態にすること。その意味からすると、真っ先に上記の pop という具体的な関数を、関数の引数として渡せるように変更することが思い浮かぶ。
sumFourElem' :: Num a => (Stack a -> (a, Stack a))
-> Stack a
-> (a, Stack a)
sumFourElem' f = \stack0 ->
let (x1, stack1) = f stack0
(x2, stack2) = f stack1
(x3, stack3) = f stack2
(x4, stack4) = f stack3
in (x1+x2+x3+x4, stack4)
(変更のついでに、第 2 引数 stack をラムダ抽象の引数にした。)
じぃ~と見ていると (@_@)、繰り返し表われるパターンがなんとなく見えてくる。
まず、最初に目がいくのは、
(x, stack○’) = f stack○
という形の羅列。上から下へと全く同じような格好の文字が並ぶ。
関数を適用する流れに目をやると、各々の f の前後関係に一定のパターンがあることに気がつく。下図の赤の矢印に注目すると、関数 f が適用する対象は、最初の stack0 を除けば、直前の関数 f の適用の結果、状態が変化したスタック。(もちろん let 式なので、上から下へと順番に並べているのは便宜的なもの。)
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
Stack a -> (a, Stack a) である型の関数をつなげる関数 comb は以下の通り。 (cf. Meet the Monads。この関数が Monad の >>= に相当。) 実装するときに思い起すことは、先ほど 4 回 pop を繰り返した sumFourElem を書いたとき、各々の pop は何に対して適用したのか?ということ。直前に適用された関数の結果に対して、次に続く関数は適用した。
comb :: (Stack a -> (a, Stack a))
-> (Stack a -> (a, Stack a))
-> Stack a -> (a, Stack a)
comb m n = \stack0 ->
let (_, stack1) = m stack0
(x, stack2) = n stack1
in (x, stack2)
comb 関数の引数 m, n は、sumFourElem’ 関数において、スタックに連続して適用する二つの関数に相当する。スタックへの適用は m が n に先行すると想定。よって、stack0 に m を適用した結果である stack1 に対して関数 n を適用している。今回は、返り値のタプルの第 1 要素はどうでもよく、第 2 要素が関数 n を stack 1 に適用した結果であることが重要。
ちなみに、この comb 関数をはじめて見たとき、返す関数の型に疑問を持った。 pop のような関数をつなぎたいのだから、第1引数と第 2 引数が
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 :: Num a =>
(Stack a -> (a, Stack a))
-> (Stack a -> (a, Stack a))
-> Stack a -> (a, Stack a)
comb m n = \stack0 ->
let (x1, stack1) = m stack0
(x2, stack2) = n stack1
in (x1+x2, stack2)
comb :: (Stack a -> (a, Stack a))
-> (Stack a -> (a, Stack a))
-> (a -> a -> a)
-> Stack a -> (a, Stack a)
comb m n f = \stack0 ->
let (x1, stack1) = m stack0
(x2, stack2) = n stack1
in (f x1 x2, stack2)
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
A closure, the opposite of a combinator, is a function that makes use of free variables in its definition. It 'closes' around some portion of its environment. for example
comb :: (Stack a -> (a, Stack a))
-> (a -> (Stack a -> (a, Stack a)))
-> Stack a -> (a, Stack a)
comb m n = \stack0 ->
let (x1, stack1) = m stack0
(x2, stack2) = n x1 stack1
in (x2, stack2)
最後は、返される型 Stack a -> (a, Stack a) に合わせると同時に外側の変数を参照して計算を組立てる。
pop `comb` (\x1 -> pop `comb` (\x2 -> (\stack -> (x1 + x2, stack)))) $ s
実行すると結果は、
(9,Stack [3,2,1])
括弧を省略するなら、
pop `comb` (\x1 -> pop `comb` \x2 -> \stack -> (x1 + x2, stack)) $ s
ret 関数
ところで、最後は型を合わせるために Stack a -> (a, Stack a) に値を投入した。これを毎回書くのは面倒なので、stack の内容は変化させない ret 関数を導入。(Monad クラスの return に相当。)
ret :: a -> (Stack a -> (a, Stack a))
ret x = \stack -> (x, stack)
書き直すと、
pop `comb` (\x1 -> pop `comb` \x2 -> ret(x1 + x2)) $ s
先ほど構成できなかった計算を複数行で書くなら、
print $ pop `comb` (\x1 ->
pop `comb` \x2 ->
pop `comb` \x3 ->
ret $ (x1 + x3) * x2) $ s
ここまでのコード
mainbak01.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
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_ :: StackOp a b -> StackOp a c -> StackOp a c
comb_ m n = m `comb` (\_ -> n)
ret :: b -> StackOp a b
ret x = \stack -> (x, stack)
これで上記 topis 関数を使った関数を実行できるようになった。
push, empty もつなげられるように変更
ところで、Stack モジュールの push 関数は次のような定義だった。
push :: a -> Stack a -> Stack a
push x (Stack xs) = Stack (x:xs)
これに対して、comb 関数の型は、
comb :: StackOp a b -> (b -> StackOp a c) -> StackOp a c
別名の定義は、
type StackOp a b = Stack a -> (b, Stack a)
上記の型に合わせるには push 関数を変更する必要がある。
push' :: a -> Stack a -> ((), Stack a)
push' x (Stack xs) = ((), Stack (x:xs))
これで push’ 関数の第 1 引数に値を適用すると、StackOp a b 型の値を受け入れる関数に渡せるようになった。
例えば、「適用するスタックを変化させない」関数を定義してみる。
print $ pop `comb` push' $ s
print $ push' 10 `comb_` pop $ s
empty' :: Stack a -> ((), Stack a)
empty' _ = ((), Stack [])
これを使って、
print $ empty' $ s
print $ push' 10 `comb_` empty' `comb_` push' 0 `comb_` push' 1 $ s
結果は、
((),Stack [])
((),Stack [1,0])
ここまでのコード
stack.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
class MyMonad m where
comb :: m a -> (a -> m a) -> m a
ret :: a -> m a
instance MyMonad (StackOp a) where
ret x = StackOp $ \stack -> (x, stack)
m `comb` n = StackOp $ \stack0 ->
let (x1, stack1) = run m stack0
(x2, stack2) = run (n x1) stack1
in (x2, stack2)
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