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 モナド版との違いは in3canReachIn3 関数を 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手版と同様に inManycanReachInMany 関数を 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 メソッドで要素毎のカウント値を簡単に取得できます。

異なる要素同士の組み合わせは、今回 nCopiescombinationsfindAll を使って取得しました。

下記で 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 では groupingBycounting メソッドを組み合わせて 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 と同じです。

なお、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.