Carpをチョット触るその4

Carpをチョット触るその3 - gos-k’s blog

文法とか

https://github.com/carp-lang/Carp/blob/master/docs/LanguageGuide.md を見ながら文法の確認。

特殊形式は fn let do if while ref address set! the の9個だが(これで全部?)流石にそのままでは不便で、core/Macros.carpに色々ある。

例えば条件分岐だと cond when unless case があって、本体に一つの式しか取れないので when-do も定義されている。(unless-doはないのか?)

反復だと for foreach while-do forever-do

fib

たいして変わらないけど、一応フィボナッチ数もやってみる。

(defn fib [x]
  (cond
    (= x 0) 0
    (= x 1) 1
    (+ (fib (- x 1)) (fib (- x 2)))))

(defn main []
  (print* (fib 40) @"\n"))
time carp -x fib.carp
102334155

real    0m21.038s
user    0m17.416s
sys 0m0.480s

比較対象としてCommon Lisp版。処理系はSBCL 1.5.6

(defun fib (x)
  (cond
    ((= x 0) 0)
    ((= x 1) 1)
    (t (+ (fib (1- x)) (fib (- x 2))))))

(defun main ()
  (pprint (fib 40)))
time ros run -l fib.lisp -e "(main)" -q
102334155
real    0m8.985s
user    0m7.405s
sys 0m0.109s

2倍位Carpが遅いがまあこんなもんなのかな? Carpの出力コードのfib関数はこんな感じ。

int fib(int x) {
    int _39;
    bool _8 = Int__EQ_(x, 0);
    if (_8) {
        int _11 = 0;
        _39 = _11;
    } else {
        int _37;
        bool _17 = Int__EQ_(x, 1);
        if (_17) {
            int _20 = 1;
            _37 = _20;
        } else {
            Lambda _23 = { .callback = fib, .env = NULL, .delete = NULL, .copy = NULL }; //Sym fib LookupRecursive
            int _27 = Int__MINUS_(x, 1);
            int _28 = _23.env ? ((int(*)(LambdaEnv, int))_23.callback)(_23.env, _27) : ((int(*)(int))_23.callback)(_27);
            Lambda _29 = { .callback = fib, .env = NULL, .delete = NULL, .copy = NULL }; //Sym fib LookupRecursive
            int _33 = Int__MINUS_(x, 2);
            int _34 = _29.env ? ((int(*)(LambdaEnv, int))_29.callback)(_29.env, _33) : ((int(*)(int))_29.callback)(_33);
            int _35 = Int__PLUS_(_28, _34);
            int _36 = _35;
            _37 = _36;
        }
        int _38 = _37;
        _39 = _38;
    }
    return _39;
}

再帰呼び出しでちょっと遠回りしてるが、まだまだシンプルで素直に読める。

CFFI

出力がCなのでCFFIもサポート。 CarpからCのヘッダファイルを読み込む。

  • system-include
  • local-include

Carp上での型定義。

  • register
  • register-type

これやってて混乱するのはString型に感することで、Carpの String はCの char * に対応する。( const char * か?) それだけならいいが、Carpの文字列リテラル&String でありCの char * * に対応する。 なので例えば、

(system-include "stdio.h")
(register puts (Fn [String] Int))

(defn main []
  (puts "Hello, world!"))

と書きたくなるんだけど、Carpのputsに渡しているのが文字列リテラルで型が &String なのに対して、Cのputsは char * でCarpの String だから素直にいかない。 この辺について見てると、core/IO.carpcore/carp_io.h でprintln関数について、

void IO_println(String *s) { puts(*s); }

とやってCarpの &String で受け取って、Cの関数でデリファレンスするという回りくどいことをするもの、らしい。 Carp側だと str で型を合わせられる。

(system-include "stdio.h")
(register puts (Fn [String] Int))

(defn main []
  (puts (str "Hello, world!")))
carp -x cffi.carp
Hello, world!
[RUNTIME ERROR] '"./out/Untitled"' exited with return value 14.

putsの返り値がそのままmainに返ってエラーがでてるけど、一応動作はする。