Arrow (Kleisli) で List モナド - Haskell, Frege, Scalaz
「Scalaz でリストモナド - Kleisli による関数合成 」等で試してきた List モナドを使ったチェスのナイト移動の処理を Arrow (Kleisli) を使って実装し直してみました。
Arrow は計算のための汎用的なインターフェースで、モナドを扱うための Arrow として Kleisli があります。
ソースは http://github.com/fits/try_samples/tree/master/blog/20140810/
Haskell の場合
Haskell の Arrow は >>>
や <<<
で合成できるようになっています。
Kleisli はモナドを扱うための Arrow なので、下記では List モナドを返す関数 moveKnight を Kleisli へ包んで合成しています。
Kleisli から包んだ関数を取り出すには runKleisli
を使います。
3手版
まずは 3手版です。
以前の List モナド版との違いは in3
と canReachIn3
関数を Arrow で実装し直した点です。
Kleisli を使えば、モナド値が無くてもモナドを返す関数 (通常の値を取ってモナドを返す関数) を簡単に合成できるので in3
はポイントフリースタイルで定義しました。 (このため canReachIn3 関数の引数の順序が 以前のもの と異なっています)
また、通常の関数は Arrow のインスタンスなので、canReachIn3 関数の部分は単純に canReachIn3 end = runKleisli in3 >>> elem end
とする事も可能です。
move_knight.hs
import Control.Arrow type KnightPos = (Int, Int) moveKnight :: KnightPos -> [KnightPos] moveKnight (c, r) = filter onBoard [ (c + 2, r - 1), (c + 2, r + 1), (c - 2, r - 1), (c - 2, r + 1), (c + 1, r - 2), (c + 1, r + 2), (c - 1, r - 2), (c - 1, r + 2) ] where onBoard (c', r') = c' `elem` [1..8] && r' `elem` [1..8] -- 3手先の移動位置を列挙 in3 :: Kleisli [] KnightPos KnightPos in3 = Kleisli moveKnight >>> Kleisli moveKnight >>> Kleisli moveKnight -- 指定位置に3手で到達できるか否かを判定 canReachIn3 :: Arrow a => KnightPos -> a KnightPos Bool canReachIn3 end = arr (runKleisli in3) >>> arr (elem end) -- 以下でも可 -- canReachIn3 :: KnightPos -> KnightPos -> Bool -- canReachIn3 end = runKleisli in3 >>> elem end main = do print $ runKleisli in3 $ (6, 2) print $ canReachIn3 (6, 1) $ (6, 2) print $ canReachIn3 (7, 3) $ (6, 2)
実行結果
> runghc move_knight.hs [(8,1),(8,3),・・・ ・・・ ,(3,4),(3,8)] True False
N手版
3手版と同様に inMany
と canReachInMany
関数を Arrow で実装し直してみました。
move_knight_many.hs
・・・ -- N手先の移動位置を列挙 inMany :: Int -> Kleisli [] KnightPos KnightPos inMany x = foldr (>>>) returnA (replicate x (Kleisli moveKnight)) -- 指定位置にN手で到達できるか否かを判定 canReachInMany :: Arrow a => Int -> KnightPos -> a KnightPos Bool canReachInMany x end = arr (runKleisli (inMany x)) >>> arr (elem end) -- 以下でも可 -- canReachInMany :: Int -> KnightPos -> KnightPos -> Bool -- canReachInMany x end = runKleisli (inMany x) >>> elem end main = do print $ runKleisli (inMany 3) $ (6, 2) print $ canReachInMany 3 (6, 1) $ (6, 2) print $ canReachInMany 3 (7, 3) $ (6, 2)
実行結果
> runghc move_knight_many.hs [(8,1),(8,3),・・・ ・・・ ,(3,4),(3,8)] True False
Frege の場合
Frege は Haskell とほとんど同じ実装になりますが、下記の点が異なります。
>>>
の代わりに.
で Arrow を合成runKleisli
の代わりにrun
を使用
なお、.
は >>>
と合成の向きが異なります。
3手版
3手版です。
move_knight.fr
package sample.MoveKnight where import frege.control.Arrow import frege.control.arrow.Kleisli type KnightPos = (Int, Int) moveKnight :: KnightPos -> [KnightPos] moveKnight (c, r) = filter onBoard [ (c + 2, r - 1), (c + 2, r + 1), (c - 2, r - 1), (c - 2, r + 1), (c + 1, r - 2), (c + 1, r + 2), (c - 1, r - 2), (c - 1, r + 2) ] where onBoard (c', r') = c' `elem` [1..8] && r' `elem` [1..8] -- 3手先の移動位置を列挙 in3 :: Kleisli [] KnightPos KnightPos in3 = Kleisli moveKnight . Kleisli moveKnight . Kleisli moveKnight -- 指定位置に3手で到達できるか否かを判定 canReachIn3 :: Arrow a => KnightPos -> a KnightPos Bool canReachIn3 end = arr (elem end) . arr in3.run -- 以下でも可 -- canReachIn3 :: KnightPos -> KnightPos -> Bool -- canReachIn3 end = elem end . in3.run main args = do println $ in3.run $ (6, 2) println $ canReachIn3 (6, 1) $ (6, 2) println $ canReachIn3 (7, 3) $ (6, 2)
実行結果
> java -jar frege3.21.586-g026e8d7.jar move_knight.fr ・・・ > java -cp .;frege3.21.586-g026e8d7.jar sample.MoveKnight [(8, 1), (8, 3), ・・・ ・・・ (3, 4), (3, 8)] true false
N手版
N手版です。
move_knight_many.fr
package sample.MoveKnightMany where ・・・ -- N手先の移動位置を列挙 inMany :: Int -> Kleisli [] KnightPos KnightPos inMany x = foldr (.) id (replicate x (Kleisli moveKnight)) -- 指定位置にN手で到達できるか否かを判定 canReachInMany :: Arrow a => Int -> KnightPos -> a KnightPos Bool canReachInMany x end = arr (elem end) . arr (inMany x).run -- 以下でも可 -- canReachInMany :: Int -> KnightPos -> KnightPos -> Bool -- canReachInMany x end = elem end . (inMany x).run main args = do println $ (inMany 3).run $ (6, 2) println $ canReachInMany 3 (6, 1) $ (6, 2) println $ canReachInMany 3 (7, 3) $ (6, 2)
実行結果
> java -jar frege3.21.586-g026e8d7.jar move_knight_many.fr ・・・ > java -cp .;frege3.21.586-g026e8d7.jar sample.MoveKnightMany [(8, 1), (8, 3), ・・・ ・・・ (3, 4), (3, 8)] true false
Scalaz の場合
最後に Scalaz を使った Scala による実装です。
Haskell と同様に >>>
で Arrow を合成できるようになっています。
3手版
3手版です。
MoveKnight.scala
package sample import scalaz._ import Scalaz._ object MoveKnight extends App { type KnightPos = Tuple2[Int, Int] val inRange = (p: Int) => 1 to 8 contains p val moveKnight = (p: KnightPos) => List( (p._1 + 2, p._2 - 1), (p._1 + 2, p._2 + 1), (p._1 - 2, p._2 - 1), (p._1 - 2, p._2 + 1), (p._1 + 1, p._2 - 2), (p._1 + 1, p._2 + 2), (p._1 - 1, p._2 - 2), (p._1 - 1, p._2 + 2) ).filter { case (x, y) => inRange(x) && inRange(y) } // 3手先の移動位置を列挙 val in3 = Kleisli(moveKnight) >>> Kleisli(moveKnight) >>> Kleisli(moveKnight) // 以下でも可 // val in3 = Kleisli(moveKnight) >==> moveKnight >==> moveKnight // 指定位置に3手で到達できるか否かを判定 val canReachIn3 = (end: KnightPos) => in3.run >>> { xs => xs.contains(end) } in3 (6, 2) |> println (6, 2) |> canReachIn3 (6, 1) |> println (6, 2) |> canReachIn3 (7, 3) |> println }
実行結果
> gradle run MoveKnight :compileJava UP-TO-DATE :compileScala UP-TO-DATE :processResources UP-TO-DATE :classes UP-TO-DATE :run List((8,1), (8,3), ・・・ ・・・ ・・・, (3,4), (3,8)) true false
N手版
N手版です。
MoveKnightMany.scala
package sample import scalaz._ import Scalaz._ object MoveKnightMany extends App { ・・・ // N手先の移動位置を列挙 val inMany = (x: Int) => List.fill(x) { Kleisli(moveKnight) }.reduce { (a, b) => a >>> b } // 以下でも可 // val inMany = (x: Int) => List.fill(x) { Kleisli(moveKnight) }.reduce { (a, b) => a >=> b } // 指定位置にN手で到達できるか否かを判定 val canReachInMany = (x: Int) => (end: KnightPos) => inMany(x).run >>> { xs => xs.contains(end) } (6, 2) |> inMany(3) |> println (6, 2) |> canReachInMany(3)(6, 1) |> println (6, 2) |> canReachInMany(3)(7, 3) |> println }
実行結果
> gradle run -Pmany MoveKnightMany :compileJava UP-TO-DATE :compileScala UP-TO-DATE :processResources UP-TO-DATE :classes UP-TO-DATE :run List((8,1), (8,3), ・・・ ・・・ ・・・, (3,4), (3,8)) true false
なお、ビルドと実行には下記のような Gradle ビルド定義ファイルを使用しました。
build.gradle
apply plugin: 'application' apply plugin: 'scala' repositories { mavenCentral() } dependencies { compile 'org.scala-lang:scala-library:2.11.2' compile 'org.scalaz:scalaz-core_2.11:7.1.0' } if (!hasProperty('many')) { println 'MoveKnight' mainClassName = 'sample.MoveKnight' } else { println 'MoveKnightMany' mainClassName = 'sample.MoveKnightMany' }
ジニ不純度の算出 - Groovy, Scala , Java 8, Frege
書籍 「集合知プログラミング」 の 「7章 決定木によるモデリング」 にあったジニ不純度(ジニ係数)の計算を下記の JVM 言語で関数言語的に実装してみました。
今回のソースは http://github.com/fits/try_samples/tree/master/blog/20140601/
はじめに
ジニ不純度の算出は、下記の (1) と (2) で異なるアイテムを取り出す確率を求める事になります。
- (1) ある集合から 1つアイテムを取り出す
- (2) 取り出したアイテムを戻して再度 1つアイテムを取り出す
例えば、["A", "B", "B", "C", "B", "A"]
のような集合の場合に A、B、C を取り出す確率はそれぞれ以下のようになります。
A = 2/6 = 1/3 B = 3/6 = 1/2 C = 1/6
ここで、ジニ不純度は以下のような 2通りの方法で算出できます。
(下記の XY
は (1) で X が出て (2) で Y が出る確率を表している)
(a) ジニ不純度 = 1 - (AA + BB + CC) = 1 - (1/3 × 1/3 + 1/2 × 1/2 + 1/6 × 1/6) = 11/18 = 0.61 (b) ジニ不純度 = AB + AC + BA + BC + CA + CB = 1/3 × 1/2 + 1/3 × 1/6 + ・・・ = 11/18 = 0.61
(a) の方がシンプルな実装になると思います。
Groovy で実装
それではそれぞれの言語で実装してみます。
Groovy では countBy
メソッドで要素毎のカウント値を簡単に取得できます。
異なる要素同士の組み合わせは、今回 nCopies
、combinations
、findAll
を使って取得しました。
下記で list.countBy {it}
の結果は [A:2, B:3, C:1]
、
nCopies(2, list.countBy { it })
の結果は [[A:2, B:3, C:1], [A:2, B:3, C:1]]
、
nCopies(2, list.countBy { it }).combinations()
の結果は [[A=2, A=2], [B=3, A=2], ・・・, [B=3, C=1], [C=1, C=1]]
となります。
gini.groovy
import static java.util.Collections.nCopies // (a) 1 - (AA + BB + CC) def giniA = { xs -> 1 - xs.countBy { it }*.value.sum { (it / xs.size()) ** 2 } } // (b) AB + AC + BA + BC + CA + CB def giniB = { xs -> nCopies(2, xs.countBy { it }).combinations().findAll { // 同じ要素同士の組み合わせを除外 it.first().key != it.last().key }.sum { (it.first().value / xs.size()) * (it.last().value / xs.size()) } } def list = ['A', 'B', 'B', 'C', 'B', 'A'] println giniA(list) println giniB(list)
実行結果
> groovy gini.groovy 0.61111111112222222222 0.61111111112222222222
Scala で実装
Scala では Groovy の countBy に該当するメソッドが無さそうだったので groupBy
を使いました。
List で combinations(2)
とすればリスト内要素の 2要素の組み合わせ (下記では AB、AC、BC の 3種類の組み合わせ) を取得できます。
下記で list.groupBy(identity)
の結果は Map(A -> List(A, A), C -> List(C), B -> List(B, B, B))
となります。
gini.scala
import scala.math.pow // (a) 1 - (AA + BB + CC) val giniA = (xs: List[_]) => 1 - xs.groupBy(identity).mapValues( v => pow(v.size.toDouble / xs.size, 2) ).values.sum // (b) AC × 2 + AB × 2 + CB × 2 val giniB = (xs: List[_]) => xs.groupBy(identity).mapValues( v => v.size.toDouble / xs.size ).toList.combinations(2).map( x => x.head._2 * x.last._2 * 2 ).sum val list = List("A", "B", "B", "C", "B", "A") println( giniA(list) ) println( giniB(list) )
実行結果
> scala gini.scala 0.6111111111111112 0.611111111111111
Java 8 で実装
Java 8 の Stream API では groupingBy
と counting
メソッドを組み合わせて collect
すると要素毎のカウントを取得できます。
要素の組み合わせを取得するようなメソッドは無さそうだったので自前で実装しました。
下記で countBy(list)
の結果は {A=2, B=3, C=1}
、
combination(countBy(list))
の結果は [[A=2, B=3], [A=2, C=1], [B=3, A=2], [B=3, C=1], [C=1, A=2], [C=1, B=3]]
のようになります。
Gini.java
import static java.util.stream.Collectors.*; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Stream; class Gini { public static void main(String... args) { List<String> list = Arrays.asList("A", "B", "B", "C", "B", "A"); System.out.println( giniA(list) ); System.out.println( giniB(list) ); } // (a) 1 - (AA + BB + CC) private static double giniA(List<String> xs) { return 1 - countBy(xs).values().stream().mapToDouble( x -> Math.pow(x.doubleValue() / xs.size(), 2) ).sum(); } // (b) AB + AC + BA + BC + CA + CB private static double giniB(List<String> xs) { return combination(countBy(xs)).stream().mapToDouble( s -> s.stream().mapToDouble( t -> t.getValue().doubleValue() / xs.size() ).reduce(1.0, (a, b) -> a * b ) ).sum(); } private static <T> Map<T, Long> countBy(Collection<T> xs) { return xs.stream().collect(groupingBy(Function.identity(), counting())); } private static <T, S> Collection<? extends List<Map.Entry<T, S>>> combination(Map<T, S> data) { return data.entrySet().stream().flatMap( x -> data.entrySet().stream().flatMap ( y -> (x.getKey().equals(y.getKey()))? Stream.empty(): Stream.of(Arrays.asList(x, y)) ) ).collect(toList()); } }
実行結果
> java Gini 0.6111111111111112 0.611111111111111
Frege で実装
Frege の group
関数では連続した同じ要素をグルーピングしますので sort
してから使う必要があります。
下記で、group . sort $ list
の結果は [["A", "A"], ["B", "B", "B"], ["C"]]
となります。
組み合わせ (AB, AC 等) の確率計算にはリスト内包表記を使ってみました。
gini.fr
package sample.Gini where import frege.prelude.Math (**) import Data.List size = fromIntegral . length -- (a) 1 - (AA + BB + CC) giniA xs = (1 - ) . sum . map calc . group . sort $ xs where listSize = size xs calc x = (size x / listSize) ** 2 -- (b) AB + AC + BA + BC + CA + CB giniB xs = fold (+) 0 . calcProb . map prob . group . sort $ xs where listSize = size xs prob ys = (head ys, size ys / listSize) calcProb zs = [ snd x * snd y | x <- zs, y <- zs, fst x /= fst y] main args = do let list = ["A", "B", "B", "C", "B", "A"] println $ giniA list println $ giniB list
実行結果
> java -cp .;frege3.21.586-g026e8d7.jar sample.Gini 0.6111111111111112 0.611111111111111 runtime ・・・
備考
giniB 関数の fold (+) 0
の部分は sum
でも問題ないように思うのですが 、sum を使うと下記のようなエラーが発生しました。
giniB 関数で sum を使った場合のエラー内容
E sample.fr:14: inferred type is more constrained than expected type inferred: (Real t17561,Show t17561) => [String] -> IO () expected: [String] -> IO ()
ちなみに、ほぼ同じコードが Haskell で動作するのですが、Haskell の場合は sum を使っても問題ありませんでした。 (gini.hs 参照)
Frege 上で Java クラスを使用する
前回、Frege で Functor や Applicative を試しましたが、今回は Frege のソース内で Java クラスを使用してみました。
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20130901_3/
はじめに
一部の Java クラスは初めから Frege のソース内で使用できるようになっていますが、それ以外の Java クラスを使用するには、下記のような data 宣言を行う必要があります。(厳密な定義は Frege の仕様書をご覧ください)
data <データタイプ名> <型変数・・・> = [mutable | pure] native <Javaタイプ名> where [pure] native <関数名> [<Javaメソッド名など>] :: <型シグネチャ・・・> ・・・
補足事項は下記。
- Immutable(不変)クラスや副作用の無いメソッドに対しては pure native を指定
- pure native では無いメソッド(オブジェクトの状態を変化させたりするメソッド等)に対しては IO や ST モナドを返すようにする
- Java インスタンスメソッドを使う場合は第一引数を自身のデータタイプとする
- "関数名" と "Javaメソッド名" が等しい場合は "Javaメソッド名" を省略可能
Immutable(不変)クラスの場合
まずは Java の Immutable クラスを使用する場合です。
java.math.BigDecimal クラスを題材に、下記のような処理を Frege で実装してみました。(ちなみに java.math.BigInteger の方は Integer として Frege 内で使えます)
- (1) 文字列を引数に取るコンストラクタ
- (2) toString メソッド
- (3) add メソッド
- (4) add メソッド(
+
関数として使用) - (5) multiply メソッド(
*
関数として使用)
bigdecimal_sample.fr
package sample.BigDecimalSample where data BigDecimal = pure native java.math.BigDecimal where -- (1) pure native new :: String -> BigDecimal -- (2) pure native toString :: BigDecimal -> String -- (3) pure native add :: BigDecimal -> BigDecimal -> BigDecimal -- (4) pure native (+) add :: BigDecimal -> BigDecimal -> BigDecimal -- (5) pure native (*) multiply :: BigDecimal -> BigDecimal -> BigDecimal main args = do let num1 = BigDecimal.new "100" let num2 = BigDecimal.new "50" putStrLn $ (num1.add num2).toString putStrLn $ (num1.+ num2).toString putStrLn $ (num1.* num2).toString
実行結果
> java -cp .;fregec.jar sample.BigDecimalSample 150 150 5000 runtime ・・・
Num の型インスタンス
上記処理で num1.+ num2
では無く num1 + num2
とするには BigDecimal を Num の型インスタンス宣言してやる必要があります。
今回、Num の型インスタンス宣言を行うには下記のような関数定義が必要でした。
- (+)
- (-)
- (*)
- one
- zero
- fromInt
- hashCode
- <=>
bigdecimal_sample2.fr (Num 型インスタンス宣言版)
package sample.BigDecimalSample2 where data BigDecimal = pure native java.math.BigDecimal where pure native new :: String -> BigDecimal pure native toString :: BigDecimal -> String pure native zero java.math.BigDecimal.ZERO :: BigDecimal pure native one java.math.BigDecimal.ONE :: BigDecimal pure native (+) add :: BigDecimal -> BigDecimal -> BigDecimal pure native (-) subtract :: BigDecimal -> BigDecimal -> BigDecimal pure native (*) multiply :: BigDecimal -> BigDecimal -> BigDecimal pure native hashCode :: BigDecimal -> Int pure native compareTo :: BigDecimal -> BigDecimal -> Int instance Ord BigDecimal where a <=> b = (a.compareTo b).<=> 0 instance Num BigDecimal where pure native fromInt java.math.BigDecimal.valueOf :: Int -> BigDecimal main args = do let num1 = BigDecimal.new "100" let num2 = BigDecimal.new "50" putStrLn $ (num1 + num2).toString putStrLn $ (num1 * num2).toString putStrLn $ (num1 - num2).toString
実行結果
> java -cp .;fregec.jar sample.BigDecimalSample2 150 5000 50 runtime ・・・
Mutable(可変)クラスの場合
次は Java の Mutable クラスを使用する場合です。
題材として良さそうなクラスが思い浮かばなかったので、とりあえず java.awt.Point を使ってみました。
Mutable クラスの場合は基本的に pure を付けない native な関数を定義して IO や ST モナドを返すようにします。
IO モナド版
まずは IO モナド版です。
new や toString 等で IO <データタイプ> を返すようにします。(今回は IO Point)
Java のインスタンスメソッドに対する関数(toString や move 等)の第一引数は IO モナドでは無く普通の値(今回の場合は Point)となります。
とりあえず下記のような処理を実装してみました。
- (1) new で x=10, y=20 へ設定
- (2) move で x=20, y=30 へ移動
- (3) translate で x を +5、y を +3
point_sample.fr (IOモナド版)
package sample.PointSample where data Point = mutable native java.awt.Point where native new :: Int -> Int -> IO Point native toString :: Point -> IO String native move :: Point -> Int -> Int -> IO () native translate :: Point -> Int -> Int -> IO () main args = do -- (1) p <- Point.new 10 20 -- (2) p.move 20 30 -- (3) p.translate 5 3 p.toString >>= putStrLn
実行結果
> java -cp .;fregec.jar sample.PointSample java.awt.Point[x=25,y=33] ・・・
ST モナド版
次に ST モナド版です。
new が ST s (Mutable s Point) を返し、toString 等の第一引数が Mutable s Point となっている点が IO モナド版との違いです。
point_sample2.fr (STモナド版)
package sample.PointSample2 where data Point = mutable native java.awt.Point where native new :: Int -> Int -> ST s (Mutable s Point) native toString :: Mutable s Point -> ST s String native move :: Mutable s Point -> Int -> Int -> ST s () native translate :: Mutable s Point -> Int -> Int -> ST s () sample :: Mutable s Point -> ST s String sample p = do p.move 20 30 p.translate 5 3 p.toString main args = do Point.new 10 20 >>= sample >>= putStrLn
実行結果
> java -cp .;fregec.jar sample.PointSample2 java.awt.Point[x=25,y=33] ・・・
補足
move や translate 等のオブジェクトの状態を変化させるようなメソッドを一切使わないのであれば java.awt.Point を pure native とする事も可能です。
point_sample3.fr
package sample.PointSample3 where data Point = pure native java.awt.Point where pure native new :: Int -> Int -> Point pure native toString :: Point -> String main args = do let p = Point.new 10 20 putStrLn $ p.toString
JVM用の純粋関数型言語 Frege で Applicative Functor を使用
Frege は Haskell によく似た JVM 用の純粋関数型プログラム言語です。
なかなか面白そうな言語だったので、関数を Applicative として使うサンプル (書籍「すごいHaskellたのしく学ぼう! 」より) を試してみました。
ソースは http://github.com/fits/try_samples/tree/master/blog/20130810/
Frege の使い方
Frege を使うには、まず http://code.google.com/p/frege/downloads/list から JAR ファイル (例 frege3.21.107-g4bd09eb.jar) をダウンロードしてきます。
カレントディレクトリに fregec.jar というファイル名で保存した場合、下記のようにすればコンパイル処理を実行できます。 (-d で出力先を指定しなければ、カレントディレクトリに Java ソース・クラスファイルが出力されます)
コンパイル
> java -jar fregec.jar [オプション] <ソースファイル>
実行は、下記のように fregec.jar をクラスパスに追加してコンパイル処理で出力された Java クラスを実行するだけです。
実行 (Windows の場合)
> java -cp .;fregec.jar <クラス名>
単純な処理の実行
それでは 3 * 5 の結果を出力するだけの単純なプログラム(下記)をコンパイル・実行してみます。
func_sample.fr
package sample.FuncSample where main args = do let f = (*3) putStrLn $ show $ f 5
次のような点が Haskell と異なります。
- package <クラス名> where が必要
- main に引数(上記の args)が必要
ちなみに、package で指定したクラス名が Java ソース化される際のクラス名・パッケージ名に使われます。
下記のようにコンパイルすると sample ディレクトリへ FuncSample.java や FuncSample.class 等が出力されます。
コンパイル
> java -jar func_sample.fr calling: javac -cp fregec.jar;. -d . -encoding UTF-8 ./sample/FuncSample.java runtime 4.58 wallclock seconds.
実行結果はこのようになります。
実行結果
> java -cp .;fregec.jar sample.FuncSample 15 runtime 0.068 wallclock seconds.
関数を Functor として使う
次は、関数を Functor として使ってみます。
Haskell による実装
まず Haskell で実装してみました。 +100 した後 *3 する処理に 1 を与えた結果を出力します。
functor_sample.hs (Haskell 版)
main = do let f = fmap (*3) (+100) putStrLn $ show $ f 1 print $ f 1
実行結果 (Haskell 版)
> runghc functor_sample.hs 303 303
Frege による実装
それでは本題の Frege による実装です。
Frege には今のところ、関数 "(->) r" に対する Functor のインスタンス宣言が無いようなので自前で行う必要がありました。(見落としているだけかもしれませんが)
なお、Haskell の print 関数は改行しますが、Frege の print 関数は改行しないので代わりに println 関数を使います。
functor_sample.fr (Frege 版)
package sample.FunctorSample where instance Functor ((->) r) where fmap = (.) main args = do let f = fmap (*3) (+100) putStrLn $ show $ f 1 println $ f 1
コンパイルと実行結果 (Frege 版)
> java -jar fregec.jar functor_sample.fr ・・・ > java -cp .;fregec.jar sample.FunctorSample 303 303 runtime 0.072 wallclock seconds.
ちなみに、"instance Functor ((->) r)・・・" を定義しなかった場合、コンパイル時に下記のようなエラーが出る事になります。
E functor_sample.fr:6: -> Int is not an instance of Functor
関数を Applicative として使う
最後に関数を Applicative として使ってみます。
Haskell による実装
Haskell 版は下記の通りです。 *3 した結果と +10 した結果を合計する処理に 4 を渡して出力します。( (3 * 4) + (10 + 4) = 26 )
applicative_sample.hs (Haskell 版)
import Control.Applicative main = do let f = (+) <$> (*3) <*> (+10) print $ f 4
実行結果 (Haskell 版)
> runghc applicative_sample.hs 26
Frege による実装
それでは Frege 版です。 Functor と同様に関数 "(->) r" に対する Applicative のインスタンス宣言も自前で行う必要があるようです。
applicative_sample.fr (Frege 版)
package sample.ApplicativeSample where instance Functor ((->) r) where fmap = (.) instance Applicative ((->) r) where return x = (\_ -> x) f <*> g = \x -> f x (g x) main args = do let f = (+) <$> (*3) <*> (+10) println $ f 4
コンパイルと実行結果 (Frege 版)
> java -jar fregec.jar applicative_sample.fr ・・・ > java -cp .;fregec.jar sample.ApplicativeSample 26 runtime 0.083 wallclock seconds.