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