OCamlで作ったgoma言語

この記事はML Advent Calendar 2014 9日目の記事です。 前 SMLでソートMllexを使ってみる。あるいはlexユーザーに対するmllexの解説

MLアドベントカレンダーが危ない。ということで、今日は、goma言語の紹介をチャラっと書いてみます。

URLはこちら https://github.com/hsk/goma

goma言語はOCamlで作ったC++を出力するトランスレータ言語です。 ATSに影響を受けて、C言語を出力するプログラミング言語を作ってみようと思ったのですが、C++のほうが楽だし、C++でよいや、いやむしろC++への出力の方がよいのではないか?

等と考えてC++を出力しています。

特徴

  • goma言語の特徴は極めて小さい構成。
  • 型システムがGoLangっぽい。
  • C言語に近い構文。

となっております。

型システム

GoLangインターフェイスを使った型システムはなかなか優れており、ビジターパターンのようなことが簡単に出来ます。しかも、後付けで関数を追加出来るような感じで強力です。 Haskellの型クラスのようなことをしつつ、オブジェクト指向っぽい事が出来るイメージであり強力なわけです。

で、gomaでは、そのインターフェイスの仮想テーブルと型にIDを付ける事で、多体性を実現したら速いんじゃないかと思って実装してみたわけです。 でもって、メモリは食うけど速そうって言う結論が出たって感じだったと思います。

それと、未来的な事を考えると、GPGPUあるいはFPGA等で、オブジェクトのメソッド検索を並列処理で検索すると速いとかいう未来もあるのかもしれないなと思ったりしてCPUとGPUが同じメモリを共有して弄るアーキティクチャなどものびて来ているようで、昔だとAMIGAが全部グラフィックスメモリっていう構成でDMA転送が速いから高速に動くみたいなのがあったなぁっと思ったりしたのでした。

実際に使ってみる

えーとまずは、ハローワールドです。

example/hello.goma

include stdio.h
main():int = {
  printf("hello world\n")
  return 0
}

セミコロン書かなくていい感じです。

変換して実行してみます。

$ ./gomac example/hello.goma example/hello.cpp
$ g++ example/hello.cpp -o hello
$ ./hello
hello world!

こんな感じになります。出力結果はlook like this:

example/hello.cpp

#include <stdio.h>
int main() {
  printf("hello world!\n");
  return 0;
}

オッケー。

なに?C++のプログラムなのに、printf使うなって?良いんだよ便利だから。

あとは、example見てくださいでもいいんだけど、ちゃんと書け俺。

計算機とかですね。

calc.goma

include "../lib/core.h"
include stdio.h

E class ()
E :> EInt (x:int)
E :> EAdd (x:*E,y:*E)
E :> EMul (x:*E,y:*E)

Eval   trait { eval():int }
Eval :> EInt { eval():int = return @x }
Eval :> EAdd { eval():int = return (*@x)|>Eval.eval() + (*@y)|>Eval.eval() }
Eval :> EMul { eval():int = return (*@x)|>Eval.eval() * (*@y)|>Eval.eval() }

main():int = {
  
  i:EInt(1)
  printf("eval 1 = %d\n", i|>Eval.eval())

  add:EAdd(new EInt(1), new EInt(2))
  printf("eval 1 + 2 = %d\n", add|>Eval.eval())

  mul:EMul(new EAdd(new EInt(1),new EInt(2)), new EInt(111))
  printf("eval (1 + 2) * 111 = %d\n", mul|>Eval.eval())

  return 0
}

なんか、golangっぽいっしょ。

Evalトレイトを定義して、Evalトレイトを継承したかんじで、色んな型の実装を後から定義して、動くと言う事ですよ。素晴らしいじゃないすか。

package main

import "fmt"
import "time"

type EInt struct {x int }
type EAdd struct {x E; y E}
type EMul struct {x E; y E}

type E interface { eval() int }
func (p *EInt)     eval() int { return p.x }
func (p *EAdd)     eval() int { return p.x.eval() + p.y.eval() }
func (p *EMul)     eval() int { return p.x.eval() * p.y.eval() }

func main() {

  i:= EInt{1}
  fmt.Printf("eval 1 = %d\n", i.eval())

  add:=EAdd{&EInt{1}, &EInt{2}}
  fmt.Printf("eval 1 + 2 = %d\n", add.eval())

  mul:=EMul{&EAdd{&EInt{1},&EInt{2}}, &EInt{111}}
  fmt.Printf("eval (1 + 2) * 111 = %d\n", mul.eval())

}

goで書くとこんなプログラムになり、Haskellで書くといかのようになります。

calc.hs

{-# LANGUAGE ExistentialQuantification #-}

data E = forall a. Eval a => E a
data EInt = EInt Int
data EAdd = EAdd E E
data EMul = EMul E E

class    Eval a    where eval :: a -> Int
instance Eval E    where eval (E x) = eval x
instance Eval EInt where eval (EInt x) = x
instance Eval EAdd where eval (EAdd x y) = eval x + eval y
instance Eval EMul where eval (EMul x y) = eval x * eval y
 
main = do
  let i = E(EInt 1)
  putStrLn $ "eval 1 = " ++ show (eval i)

  let add = E(EAdd (E(EInt 1)) (E(EInt 2)))
  putStrLn $ "eval 1 + 2 = " ++ show (eval add)

  let mul = E(EMul (E(EAdd (E(EInt 1)) (E(EInt 2)))) (E(EInt 111)))
  putStrLn $ "eval (1 + 2) * 111 = " ++ show (eval mul)

Scalaでも書いてみたりしたのですけど、githubの方見てみてください。

次は適当フィボナッチ(2以下は1だけじゃだめだろとかいうことで)の例です。関数で書いた例とオブジェクト使ったような例があります。

fib.goma

include "../lib/core.h"
include stdio.h

fib(a:int):int =
  if (a < 2) return 1
  else       return fib(a-2)+fib(a-1)

Fib trait {
  fib():int
}

Int class (x:int)
Int <: Fib {
  fib():int =
    if (@x < 2) return 1
    else {
      p1:Int(@x - 2)
      p2:Int(@x - 1)
      return p1|>Fib.fib() + p2|>Fib.fib()
    }
}

main():int = {

  start:long = gett()
  result:int = fib(40)
  printf("fib %d %d %ld\n", 40, result, gett() - start)

  start = gett()
  i:Int(40)
  printf("fib %d %d %ld\n", i.x, i|>Fib.fib(), gett() - start)

  return 0
}

goっぽいっしょ。これが、C++のクラスではなくて、goっぽい形で実現してますけど、メモリの確保は、インターフェイスに1つテーブルがあって、型にIDが動的に振られて、IDの箇所に関数が保存されるような仕組みで動いて結構速いのです。

以上、goma言語の適当な紹介でした。

明日は、keenさんのMllexを使ってみる。あるいはlexユーザーに対するmllexの解説です。