!kiyoka.blog.2010_03 RSSPLAIN

Related pages: !kiyoka.blog.list
15515553333333330333555555555555555555555555555555555555555555555555555555555555555555555555555555405555555555555555055555552555055555555555555555555555555555555555555555055555555555555555555555555555555555555555555555555555555555555555555555555555505555555555505555555555555555555555555555555555555555555555555555555555555505555555555555555555555555555555555555555555555555555555555555555555505
1

kiyoka日記。NendoSekkaの開発や、最近思うことなど

5

最新10件!kiyoka.blog   過去記事一覧!kiyoka.blog.list

5

kiyoka.blog_header 

1

このブログを書いている人: 西山 清香(kiyoka) - twitter: @kiyokaEXT

5

5

 

5

 

3

kiyoka.2010_03_27[Nendo] オレ言語処理系Nendoを開発する意味

3

 

3

『Just for Fun』 という答えはありきたりすぎて面白く無いかも知れないけど、実際そういう部分が一番大きい。

3

同時に、プログラミングスキルを上げたいという気持ちも当然あって、それらの動機は混在しているし、考えてみると他にも動機がいろいろ有りそうで自分自身でもはっきり説明できない所がもどかしい。

3

しかし、振りかえってみると、Nendoの開発は知的好奇心を刺激するし、同時にLispプログラミングのスキルが上がっているのは確かなので、このまま好循環で進んで行きそうだ。

3

 

3

ちなみに、ちょっとSumibiエンジンのソースを読み返してみたが、今のスキルでSumibi.orgを書き直したら随分とスッキリした短いコードが書けるだろうなあと感じた。

3

これをさらっと出来てしまうほどコードを書くスピードが速いわけではないので実際は思うだけで終わりそうだけど...

3

 

0

comment (disabled)

3

3

 

3

 

5

kiyoka.2010_03_26[Nendo] 多値の実装完了 

5

私がRubyで書いているLisp方言、 Nendoについて。

5

 

5

多値が動いた。(参考リンク: Scheme:多値EXT)

5

Chatonでつぶやいたら、さっそくShiroさんが答えて下さった。

5

 

5
 Gauche > Archives > 2010/03/24EXT 周辺のログを抜粋
5
 kiyokaの質問
5
  オレLisp処理系Nendoに多値を組み込みたいけど、どうやったらいいのか悩み中です。
5
  ちなみにNendoは継続とかサポートしていません。
5
  巷のSchemeライブラリのソースを流用したいことが多いので、できれば values と receive がそのまま動けばうれしいのですが...
5
 shiroさんからの回答
5
  >kiyoka 性能を考えないなら、guileがやってたように0個と2個以上の値を特別なオブジェクトにパックしてしまうってのでとりあえず動かすことはできますよ。
5
  受け取る値の個数を間違えた場合にその内部的な「多値オブジェクト」が見えてしまう、というのがちょっと残念ですが。
5
  (define (values . args) (match args [(val) val] [_ (make-values args)]))
5
  (define (call-with-values producer consumer) (let ((v (producer))) (if (values? v) (apply consumer (values-values v)) (consumer v))))
5
  多値オブジェクトは make-values で作成、values? で判断、 values-valuesで値リスト取り出し、と想定。
5
  (define-syntax receive (syntax-rules () ((receive vars expr body ...) (call-with-values (lambda () expr) (lambda vars body ...)))))
5

 

5

ほぼshiroさんの回答通り実装してみた。

5

残念ながらNendoには match と define-syntax がまだ装備されていないので、別の手段で実装してある。

5

make-values、values?、values-values は Rubyで書いてある。

5

 

5
ソースコード
5
(define (values . args)
5
  (case (length args)
5
    ((1)
5
     (car args))
5
    (else
5
     (make-values args))))
5
5
(define (call-with-values producer consumer)
5
  (let ((v (producer)))
5
    (if (values? v)
5
        (apply consumer (values-values v))
5
        (consumer v))))
5
5
;; srfi-8
5
(define receive
5
  (macro (vars expr . body)
5
    `(call-with-values
5
         (lambda () ,expr)
5
       (lambda ,vars ,@body))))
5

 

5
実行結果
5
R5RSの仕様書に書いてある例
5
nendo> (call-with-values
5
            (lambda () (values 4 5))
5
          (lambda (a b) b))
5
 => 5
5

 

5
nendo> (call-with-values * -)
5
 => -1
5

 

5
その他
5
nendo> (call-with-values
5
            (lambda () (values 1 2))
5
          cons)
5
 => (1 . 2)
5

 

5
nendo> (call-with-values
5
            (lambda () (values 10))
5
          list)
5
 => (10)
5

 

5
nendo> (receive all       (values)           all)
5
 => ()
5

この結果は実装依存だと思うが、Gauche 0.9と同じ動作になっている。

5

 

5
nendo> (receive (a . b)   (values 10 20 30)  (list a b))
5
 => (10 (20 30))
5

 

5

これで、Gaucheのtext.html-liteのポーティング準備が一歩前進した。

5

後は、keyword関連と list* を実装したら簡単にポーティング出来るんじゃないかと。

5

この作業のゴールとしては、Sinatraのページ記述用プラグインとしてhamlとかerb等があるが、その一つとしてS式でもページが書けることを狙っている。(Sinatraのソースを読んだら、プラグイン追加するためには、パッチを当てないとダメっぽいが...)

5

とりあえずの中間地点として、まずS式でCGIが書ける段階を目指そう。

5

 

5

COMMENTshiro

list*は簡単ですよ。 (define (list* arg . args) (if (null? args) arg (cons arg (apply list* args))))

4

COMMENTkiyoka

list* の定義は簡単なんですねー。ありがとうございます。

上記コードがNendoでも動きました。

nendo> (list* 1)

=> 1

nendo> (list* 1 2 3 4)

=> (1 2 3 . 4)

0

comment (disabled)

5

5

 

5

 

5

kiyoka.2010_03_22[Nendo] 実用アプリを作りながら、処理系を鍛えるのは重要

5

私がRubyで書いているLisp方言、 Nendoについて。

5

 

5

実はNendoの処理系を作りながら、その言語でStowspecというパッケージ管理ツールを書いてみている。

5

ShiroさんのGaucheの開発プロセスの話を聞いて、レベルは数段下だけど真似してみている。

5

実際にツールを書いてみると、デバッグ表示の仕組が一番最初に必要になったり、RSpecに定義したテストで漏れている項目がじゃんじゃん見つかる。

5

漫然と処理系を作るだけでなく、普段使うツールを作ってみると次に取り組むべき課題が自然に提示されてやりやすい。

5

 

5

Stowspecのほうは一応落ちついたので、一度Webアプリに挑戦してみたいな。

5

 

5

RubyでいうところのSinatraEXTとHamlみたいなシンプルな仕組みで、KahuaのようなS式でHTMLを書けるようになると嬉しいな。

5

まあ、そんなに急がずとも、基本的なCGIがどんな風になるのかから見てみようかな。

5

 

0

comment (disabled)

5

5

 

5

 

5

kiyoka.2010_03_19[Nendo] Nendo 0.2.0 リリース

5

私がRubyで書いているLisp方言、 Nendoについて。

5

 

5

多少は遊べるようになってきたので、バージョン 0.2.0 としてリリースしました。

2

rubygems_icon_128

5

インストール方法など、詳細はNendoを参照してください。

5

チュートリアル(書きかけ) Nendo.Tutorial、 リリースノート Nendo.ReleaseNoteもあわせて御覧ください。

5

 

0

comment (disabled)

5

5

 

5

 

5

kiyoka.2010_03_18[Nendo] 初期化スクリプトをコンパイル済にして高速化した

5

私がRubyで書いているLisp方言、 Nendoについて。

5

 

5

Nendoにはinit.nndという起動直後に読み込まれる初期化スクリプトがある。

5

中身はNendoのソースコードで、caar や caaar や let1、cond、caseなどの基本的な構文が定義してある。

5

0.2.0の時点で約600行のソースだ。

5

 

5

このファイルの読み込みが重いという問題があったのだが、かなりad-hocな方法で解決した。

5

Nendoの処理系はもともと Nendo(Lisp言語)を読み込むと、それと等価なRubyコードにトランスレートして実行する仕組みを持っている。

5

今回 init.nnd の読み込みが重いのは、パース、マクロ展開、Rubyコードへのトランスレートという非常に重いプロセスが済んだ init.nndc(コンパイル済ファイル)というファイルを生成するようにした。(拡張子のネーミングは Emacsの .el と .elc と同様にUNIXで一般的なルールです)

5

察しの通り init.nndc は生成したRubyコードが入っている。

5

 

5

さて、どれくらいの差があるか計測してみよう。

5

計測に使ったマシンは PowerBook G4 (PowerPC 1GHz) の非常に時代遅れなマシン。

5

 

5

ちなみに nop.nnd は中身がコメントのみで関数呼び出しも何もないファイル。

5
;;-*- mode: nendo; syntax: scheme -*-;;
5
;; Nothing to do.
5

 

5

 

5
init.nnd (コンパイルなし)
5
bash-3.2$ time nendo nop.nnd
5
5
real    0m10.462s
5
user    0m9.550s
5
sys     0m0.280s
5

 

5
init.nndc (コンパイル済)
5
bash-3.2$ time nendo nop.nnd
5
5
real    0m0.809s
5
user    0m0.636s
5
sys     0m0.110s
5

 

5

コンパイルしないと、起動に10秒もかかってしまってしまう。

5

最初にNendoをさわった人はここで見向きもしなくなるだろうが、コンパイル済であれば、まあギリギリOKの範囲だろう。

5

0.2.0のリリース版は自動的にコンパイル済の初期化スクリプトが選択されるようになっていますので御安心を。

5

 

0

comment (disabled)

5

5

 

5

 

5

kiyoka.2010_03_16[Nendo] Gaucheの #?= 相当のデバッグユーティリティをサポート

5

私がRubyで書いているLisp方言、 Nendoについて。

5

 

5

Gaucheには #?=S式 という形式のリーダマクロでデバッグ表示する機能がある。

5

それをNendoでもそっくりそのまま真似てみることにした。

5

結果を見て頂けるとわかるように、本当にソックリな表示になっているのがわかるだろう。

5

これで、手がGaucheに慣れてしまっている人でもNendoで遊びやすくなったかな?

5

近日中にそこそこ遊べるバージョンとして 0.2.0 をリリースするので、粘土遊びを試してみてください。

5

 

5
goshの実行例 (リファレンスマニュアルより抜粋)
5
 今のところ、作者は必要な時は古典的な「プリントスタブ」方式を使っていま
5
 す。そのためのリーダーマクロ #?=expr が定義されています。これは
5
 (debug-print expr) のように読まれ、マクロ debug-print はexprを評価して
5
 その値を返しますが、評価前と評価後にメッセージを出力します。
5

 

5
gosh> #?=(+ 2 3)
5
#?="(stdin)":1:(+ 2 3)
5
#?-    5
5
5
5
gosh> #?=(begin (print "foo") (values 'a 'b 'c))
5
#?="(stdin)":2:(begin (print "foo") (values 'a 'b 'c))
5
foo
5
#?-    a
5
#?+    b
5
#?+    c
5
a
5
b
5
c
5
gosh> (define (fact n)
5
        (if (zero? n)
5
            1
5
            (* n #?=(fact (- n 1)))))
5
fact
5
gosh> (fact 5)
5
#?="(stdin)":6:(fact (- n 1))
5
#?="(stdin)":6:(fact (- n 1))
5
#?="(stdin)":6:(fact (- n 1))
5
#?="(stdin)":6:(fact (- n 1))
5
#?="(stdin)":6:(fact (- n 1))
5
#?-    1
5
#?-    1
5
#?-    2
5
#?-    6
5
#?-    24
5
120
5

 

5
Nendoの実行例 (プログラムをファイルに保存してから実行)
5
ソース fact.nnd
5
#!/bin/sh
5
true; #-*- mode: nendo; syntax: scheme -*-;;
5
true; exec /usr/local/bin/nendo $0 $*
5
5
;; fact
5
(define (fact n)
5
  (if (zero? n)
5
      1
5
      (* n #?=(fact (- n 1)))))
5
5
(print (fact 5))
5

 

5
実行結果
5
bash$ ./fact.nnd 
5
./fact.nnd 
5
#?="./fact.nnd":9:(fact (- n 1))
5
#?="./fact.nnd":9:(fact (- n 1))
5
#?="./fact.nnd":9:(fact (- n 1))
5
#?="./fact.nnd":9:(fact (- n 1))
5
#?="./fact.nnd":9:(fact (- n 1))
5
#?-    1
5
#?-    1
5
#?-    2
5
#?-    6
5
#?-    24
5
120
5

 

0

comment (disabled)

5

5

 

5

 

5

kiyoka.2010_03_15[Life] 週3日のプログラミング

5

月水金と子供を保育所に預けている間、リハビリも兼ねてカフェでプログラミングしている。

5
 1-1216221367ByEe
5

1日4時間程度使えるので、思いのほか進捗がよろしい。

5

しかし、オレ言語みたいな 浮世離れしたプログラミングしていていいのかなぁ。とは思うけど。

5

リハビリにしては不必要に技術レベルが高いのに、実用からは外れているという…

5

どこかで実用に近いプログラミング演習にも合流予定なのではあるが…

5

 

0

comment (disabled)

5

5

 

5

 

5

kiyoka.2010_03_06[Nendo] named letをmacroで実装した

5

私がRubyで書いているLisp方言、 Nendoについて。

5

 

5

これまで、Nendoではlet等の予約語はマクロを束縛すると正しく展開できない実装だったが、ついに予約語と同名の変数にマクロを束縛しても動作するようにした。

5

そうすることで、例えば named let をmacroで定義できるようになった。

5
;; named letのサポート
5
(define let
5
  (macro lst
5
    (if (symbol? (car lst))
5
        ;; named let
5
        `(letrec ((,(first lst)
5
                   (lambda ,(map
5
                             (lambda (x)
5
                               (first x))
5
                             (second lst))
5
                     ,(third lst))))
5
           (,(first lst)
5
            ,@(map
5
               (lambda (x)
5
                 (second x))
5
               (second lst))))
5
           
5
        ;; don't touch
5
        `(let ,@lst))))
5

 

5

なぜこれをできるようにしたかというと、処理系全体のソースコードがコンパクトになる方向を目指しているためだ。

5

一般的にS式のmacroはソースコード規模を圧縮する方向に作用するため、Rubyで実装するコアはシンプルな構文のみに抑え、Lispのmacroを多用して構文を拡張した方が有利になるからだ。

5

Rubyではコード圧縮に限界がある。

5

 

5

Rubyで『named letの機能を持たない基本的なlet』と『letrec』に対応するコード生成機能が有れば、named letをNendoのmacroで記述できる。

5

例えば、下記のnamed letのサンプルコードは上のマクロで解決できる。

5

 

5
 展開前
5
(let1 total 0
5
  (let loop ((cnt 10))
5
    (if (< 0 cnt)
5
        (begin
5
          (set! total (+ total cnt))
5
          (loop (- cnt 1)))))
5
  total)
5

 

5
 展開後
5
(let ((total 0))
5
  (letrec
5
      ((loop (lambda (cnt)
5
               (if (< 0 cnt)
5
                   (begin (set! total (+ total cnt))
5
                          (loop (- cnt 1)))))))
5
    (loop 10))
5
  total)
5

 

5
 実行結果
5
55
5

 

5

恥を晒すと、本当はSchemeのdefine-syntax相当をサポートしてそれで定義するのがまっとうな方法かも知れないが、まだそこまで自分の力量が足りていないというのが正直なところ。

5

しかし、これまで実際にLisp処理系を実装するのは本当に勉強になると実感した。

5

やればやるほど、Lispの奥深さが少しづつ感じ取れる様になった。(オマケとしてRubyにも詳しくなったし…)

5

Lisp系言語を本当に習得したいという人は一度は自分のLisp処理系を実装してみることをおすすめするぞ。

5

 

0

comment (disabled)

5

5

 

5

 

5

kiyoka.2010_03_02[Nendo] set!の実装につまづく(3) =>解決

5

私がRubyで書いているLisp方言、 Nendoについて。

5

 

5

先日の記事 (kiyoka.2010_02_22『set!の実装につまづく(2)』)の続き。

5

 

5

問題のバグを修正し、各々のset!に対応するRubyコードを正常に出力できるようになった。

5

shiroさんのコメントにあったように、LispのコードをRubyに変換する処理において、サブフォームに再起して行く際にローカル変数のリストを渡していく方法で簡単に実装できた。

5

set!の第一引数の変数名がローカル変数リストにヒットすればRubyのローカル変数を使用し、ヒットしなければRubyのインスタンス変数(Lispのグローバル変数の代替品)を使う実装にした。

5

このバグ修正をする前作業として、インデント付きのRubyコード生成を生成するようにしたので見やすくなった。

5

そのままコードを貼りつけて解説する。

5

 

5
グローバル変数の更新
5

Nendo:

5
(define a-global-var 1)
5
(set! a-global-var 10)
5

生成コード(Ruby):

5
  @_a_global_var = 
5
    1
5
  @_a_global_var = 
5
    10
5

 

5
ローカル変数の更新
5

Nendo:

5
(let ((a-local-var 2))
5
  a-local-var)
5

生成コード(Ruby):

5
  begin
5
    ___lambda = lambda { |_a_local_var| 
5
        begin
5
            _a_local_var
5
          rescue => __e ; __e.set_backtrace( ["(stdin):126"] + __e.backtrace ) ; raise __e
5
        end
5
    } ; ___lambda.call(
5
        2
5
               )
5
  end
5

 

5
グローバル変数とローカル変数の混在
5

Nendo:

5
(define a-global-var 1)
5
(let ((a-local-var 3))
5
  (set! a-global-var 20)
5
  (set! a-local-var 4))
5

生成コード(Ruby):

5
  @_a_global_var = 
5
    1
5
5
  begin
5
    ___lambda = lambda { |_a_local_var| 
5
        @_a_global_var = 
5
          20
5
        _a_local_var = 
5
          4
5
    } ; ___lambda.call(
5
        3
5
               )
5
  end
5

 

5

明日は、内部defineをletrecに変換するコンパイルフェーズをNendo自身で書いてみよう。

5

 

5

ところで、実際にSchemeの仕様書を読みながら実装してみると、Schemeの仕様があまり何も規定していない実装自由度の高い仕様であることが分かる。

5

なので、どのような用途を狙うかによって、処理系ごとの規模の振れ幅は大きいだろうなと思う。

5

例えば、小さなフットプリントを狙うのか、処理系のソースコード規模最小限を狙うのか、高速な処理系を狙うのかなどで全く違った実装になる。

5

Schemeの処理系の数が余りにも多いのも頷ける。(tinyschemeとかchibischime、GaucheとかMoshとか色々ある)

5

 

0

comment (disabled)

5