!kiyoka.blog.2010_02 RSSPLAIN

Related pages: !kiyoka.blog.list
1551555222222222220222444444444444444444444444444444444444444444444444444444444334344444444433444444444444444444444444444444444444444444444444444444444440444555555555555555555555555555555555555555555555555555555555555555555555555555555555555055555555555555555555555555555055555555555555555555555505
1

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

5

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

5

kiyoka.blog_header 

1

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

5

5

 

5

 

2

kiyoka.2010_02_27[Nendo] letrecを実装した

2

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

2

 

2

やっとletrecを実装した。1時間半位で実装できた。変換処理はRubyで実装している。

2

SchemeのR5RS仕様書に内部defineはletrecに変換可能と書いてあるので、Nendoも同じ仕様で実装しよう。

2

やっぱりリスト処理はRubyよりも、Lisp系言語の方が書きやすいので、そのコンパイルフェーズはNendo自身で書きたい。

2

もっとよく考えていけば、Nendo自身で書ける部分も増えそうだけど、鶏が先か卵が先かというような、複雑な依存関係が増えそうな危険な香りもするのでちょっとずつリファクタリングする方が良いかな?

2

 

2

ちなみに、もしやと思ってGauche 0.9のcompile.scmを見てみたら、Gaucheも内部defineはletrecに変換しているようだ。

2

やっぱり、早めに内部defineは消しておき、letrec 1本に収束させた方が綺麗だからだと思う。

2

 

0

comment (disabled)

2

2

 

2

 

4

kiyoka.2010_02_22[Nendo] set!の実装につまづく(2)

4

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

4

 

4

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

4

shiroさんのコメントで間違いを御指摘いただいたのと、問題点がうまく記述できていなかったため、もううちど原点に戻って整理しよう。

4

 

4

Nendoの設計方針

4
クロージャのレキシカルスコープ等の管理はRuby処理系まかせにする
4

NendoではLispコードを等価なRubyコードにトランスレートすることで、クロージャのローカル変数(=レキシカル変数)のスコープ管理を全てRuby本体の機能にまかせることにする方針。自分で書くよりも、コード量が削減できるし、Ruby VMの最適化の恩恵にも浴することができるのではないかというのが狙い。

4

(クロージャのレキシカル変数がちゃんと動くようになったのはRuby 1.9.1からです。昔のRubyでは挙動が違うことに注意)

4

 

4
Rubyのインスタンス変数とローカル変数の両方を使う
4

Schemeのグローバル変数に相当するものをRubyのインスタンス変数 (Rubyでの表記は@var) に割当て、Schemeのローカル変数に相当するものを、Rubyのローカル変数 (Rubyでの表記はvar)に割りあてる方式でやることにした。

4

当初、Schemeのグローバル変数の機能もRubyのローカル変数だけで代用できるのではと思ったが、Rubyのローカル変数はRubyのコード実行時に割当てが決まるのではなく、Rubyコードのコンパイル時点で割当てわれてしまうため、コンパイル時に未定義の変数は永遠に未定義のままとなる。

4

これでは、トップレベル関数の相互呼出ができないとか、replでトップレベル関数の差し替えなどもできないという問題がある。

4

[実験コード]

4
  1  #!/usr/local/bin/ruby
4
     
4
     #
4
     # Rubyのローカル変数に割当てたクロージャは、pre-definedでないと呼出せない。
4
     #
4
     def test1
4
       proc1 = lambda {
4
         p "proc1(1)"
4
       }
4
       proc2 = lambda { # <--- entry point
4
         p "proc2(1)"
4
         proc1.call()
4
         p "proc2(2)"
4
 14      proc3.call()
4
         p "proc2(3)"
4
       }
4
       proc3 = lambda {
4
         p "proc3(1)"
4
       }
4
       
4
       proc2.call()
4
     end
4
     
4
     test1()
4

 

4

[実行結果]

4

14行目でproc3が未定義になる。(NameError例外)

4
"proc2(1)"
4
"proc1(1)"
4
"proc2(2)"
4
local_variable_test2.rb:14:in `block in test1': undefined local variable or method `proc3' for main:Object (NameError)
4
        from local_variable_test2.rb:21:in `call'
4
        from local_variable_test2.rb:21:in `test1'
4
        from local_variable_test2.rb:24:in `<main>'
4

 

4

補足として、test1()メソッドの入口でproc3 = nilとしておくと、上のエラーは回避できるが、Lispのrepl上では未来にユーザーがどのような変数を使うかは神のみぞ知るなので、ここでは使えない。

4

 

4

set!の実装方法

4

set!の代入対象の変数がローカル変数か、グローバル変数か、はたまた未定義かを生成後のRubyコードで動的に判断させる方法を最初に試したが、ダメだった。

4

例えば、(set! a 2) をRubyに変換したイメージを下記に示す。これではうまくいかない。

4
      begin
3
        if defined?(a) == "local-variable"
3
  3       _a = 2     # local variable
4
        elsif self.instance_variables.include?(:@_a)
3
  5       @_a = 2   # global variable
4
        else
4
          raise NameError
4
        end    
4
      rescue => __e
4
        raise __e
4
      end
4

 

4

前回の記事 (kiyoka.2010_02_21『set!の実装につまづく』) でも書いたように、Rubyのローカル変数の代入は非常に特殊で、if false then  a = 2  end の様に例え実行されなくても、それ以降は ローカル変数 a が『宣言』されたことになってしまう。(前回の記事で『ブロック内のどこかに代入が記述されただけで(実行されなくても)、ローカル変数が宣言されたことになってしまう。』と書いたのは私の勘違いでした…すみません)

4

 

3

例えば、上の実装イメージでset!が連続で記述されたら1回目と2回目の set! の挙動が変わってしまうだろう。(たぶん、1回目はグローバル変数を更新し、2回目は新しく宣言したローカル変数を更新する挙動になるだろう)

3
(define a 100)
4
(set! a 2)
4
(set! a 3)
4

 

4

下記の実験コードで試してみよう。

4

[実験コード]

4
      #/usr/local/bin/ruby
4
      
4
      def local_variable_test
4
        func1 = lambda {||
4
          print "  ---- (1) ----\n" 
4
          begin
4
            print "a               : " ; p a
4
          rescue NameError
4
            puts "NameError(1)"
4
          end
4
          print "defined?(a)     : " ; p defined?(a)
4
          print "local_variables : " ; p local_variables
4
 13       if false
4
 14         a = 1     # assign
4
 15       end
4
      
4
          print "  ---- (2) ----\n" 
4
          begin
4
            print "a               : " ; p a
4
          rescue NameError
4
            puts "NameError(2)"
4
          end
4
          print "defined?(a)     : " ; p defined?(a)
4
          print "local_variables : " ; p local_variables
4
          if false
4
            a = 1     # assign
4
          end
4
        }
4
      
4
        func1.call()
4
      end
4
      
4
      local_variable_test()
4

 

4

[実行結果]

4
bash$ ruby local_variable_test.rb
4
  ---- (1) ----
4
a               : NameError(1)
4
defined?(a)     : nil
4
local_variables : [:a, :func1]
4
  ---- (2) ----
4
a               : nil
4
defined?(a)     : "local-variable"
4
local_variables : [:a, :func1]
4

 

4

func1の ---- (1) ---- と ---- (2) ---- 部分は同じコードの繰返しなのに、13,14,15行目の実行されない代入が登場するとそれ以降ローカル変数a はNameErrorにならない。

4

Rubyの仕様ではローカル変数が宣言されるとnilが代入される様だ。(今回の問題には関係ないけど、local_variablesメソッドの出力結果が defined?(a)の結果と対応していないのが変だが、それは気にしないでおこう。Rubyのバグかな?)

4

 

4

まとめ

4

このように、たとえ実行されない代入文でも宣言となる仕様では、set!に対応するコードをRubyで動的に切りかえる事はできない。

4

よって、前回の記事でも書いた様に、LispからRubyへの変換時にその時点での対応するRubyコードに切りかえて出力しないといけない。

4

 

4

今回のset!対応は生成Rubyコードを人間に見やすくインデントするリファクタリングをやった後にやろう。

4

 

0

comment (disabled)

4

4

 

4

 

5

kiyoka.2010_02_21[Nendo] set!の実装につまづく

5

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

5

 

5

ちょっと作業する時間があったので、set!をSchemeの仕様に近づける修正にトライした。

5

つまづいたので備忘録として記事にしておく。

5
 3844653122_4b8def232e_m
5

 

5

方針

5

方針としては、Schemeのグローバル変数に相当するものをRubyのインスタンス変数 (Rubyでの表記は@var) に割当て、Schemeのローカル変数に相当するものを、Rubyのローカル変数 (Rubyでの表記はvar)に割りあてる方式でやることにした。

5

 

5

LispからRubyへのトランスレートにおいて次の様なコードを吐く様にした。

5

(以下は原理を簡単にする為に、実際にNendo処理系が出力するコードを簡略化してある。一応動く)

5

 

5
トランスレート前のLispコード
5
(define a 100)
5
(let ((a 1))
5
  (set! a 2))
5

 

5
トランスレート後のRubyコード
5
    #/usr/local/bin/ruby
5
    
5
    @_a = 100
5
    lambda {|a|
5
      begin
5
  6     if defined?(a) == "local-variable"
5
          puts "(1)"
5
  8       a = 2     # local variable
5
        elsif self.instance_variables.include?(:@_a)
5
           puts "(2)"
5
 11        @_a = 2   # global variable
5
        else
5
          raise NameError
5
        end    
5
      rescue => __e
5
        raise __e
5
      end
5
 18   p local_variables
5
    }.call(1)
5

実行結果

5
ruby localvar_test.rb 
5
(1)
5
[:a, :__e]
5

 

5

どこがダメか

5

上記のコードでは11行目が実行されるかと思いきや8行目のほうが実行される。

5

ローカル変数 a が8行目で宣言されてしまうので、6行目の判定は定義済となってしまうのだ。

5

Rubyのローカル変数の代入は非常に特殊で、ブロック内のどこかに代入が記述されただけで(実行されなくても)、ローカル変数が宣言されたことになってしまう。

5

 

5
 変数と定数 - RubyリファレンスマニュアルEXTの引用
5
   宣言は、例え実行されなくても宣言とみなされます。
5
   v = 1 if false # 代入は行われないが宣言は有効
5
   p defined?(v)  # => "local-variable"
5
   p v            # => nil
5

 

5

手ぬきの代償

5

NendoではLispコードを等価なRubyコードにトランスレートすることで、クロージャが持つローカル変数のスコープ管理などを全てRuby本体の機能にまかせるという前提だったのだが、上記のやりかたでは十分でないということになる。

5

Schemeのset!と等価なローカル変数の代入を実装するためには、そのローカル変数が定義済かどうかの判定を生成されたRubyコード自身にやらせるのでは遅すぎるということだ。

5

 

5

対策

5

Rubyへトランスレートする前のLispコード(S式)の段階でレキシカルスコープの解析を行い、それぞれのset!が出現した場所でそのローカル変数が定義済かどうかを判定して、各set!に対応するコードを動的に切りかえる。

5

多分これで実現できるだろう。

5

 

5

感想

5

Rubyのローカル変数の代入と宣言の仕様は、なかなか微妙な仕様だと思う。

5

Schemeが define(定義) と set!(代入)の役割をはっきり分離しているのに対して、Rubyはそこを分かちがたく統合してしまっている。

5

そこには、Rubyなりの設計方針でそうなっているのだとは思うが、Schemeと比べるとスッキリしないなあ。

5

Rubyの『驚き最小の法則』というのを昔聞いたことがあるが、今回のは個人的にちょっと驚いた。

5

というか、せめて宣言と代入を分離する手段も用意しておいてほしかったぞ。

5

 

5
 参考: yugui wiki - 『初めてのRuby』余った切れ端EXT
5
 yuguiさんの見解が書かれているページを見つけた。
5
 
5
  初期値 (6章余り)
5
  Column: 初期値
5
 (略)
5
  Ruby文法は妥協と折衷、損益判断により構成されています。Ruby文法がなぜ
5
  Aであるかを調べると、いつも「Bにするだけの価値があるか」という点が浮
5
  かび上がってきます。変数は基本的に代入による初期化を必須にする方針の
5
  一方で、インスタンス変数とグローバル変数についてはアクセス可能なコー
5
  ド範囲が広すぎて初期化を強制するには弊害が大きすぎたのだと考えられま
5
  す。
5

 

5

COMMENTshiro

rubyの仕様はまだよく理解できてないですが、このコードに限ればdefined?(a)がlocal-variableになるのはlambda式の仮引数が|a|になってるからじゃないでしょうか。試しに lambda {|z| ...} に変えてみたら11行目の方が実行されました。

仮引数が|a|なのは (let ((a 1)) ...) だから、ということなら、8行目の方が実行されるのが正しいですよね。

ただまあ、Schemeコードを処理する時に変数がローカルかグローバルかを判定するのはごく簡単なので (サブフォームに再帰してゆく時にローカルの変数リストを渡して行くだけ)、そうしてしまった方がうんと楽だとは思います。あと、その時点でset!に対応するコードを切り替えるのは、「動的」とは言わないと思います。コードそのものの実行時じゃないから。

5

COMMENTkiyoka

shiroさん、コメントありがとうございます。

ご指摘の通り、上記の実験に致命的な間違いがありました。

この記事での問題点の記述も微妙にずれております。

再度、問題点を整理して次の記事にしてみます。

ただ、解決方法はshiroさんのコメントのように、サブフォームにローカル変数のリストを渡す方法でいけると思います。

また、Rubyコード生成時にset!のコードに対応するRubyコードに切り替えるのは、どちらかというと「動的」でなくて「静的」ですかね。

0

comment (disabled)

5

5

 

5

 

5

kiyoka.2010_02_20[本] プログラミングClojureを読む(その2)

5
 4274067890  プログラミングClojure: Stuart Halloway, 川合史朗
5

[目次]

5
第1章 Getting Started
5
第2章 Clojureひとめぐり
5
第3章 ClojureからJavaを使う
5
第4章 シーケンスを使ったデータの統合
5
第5章 関数型プログラミング
5
第6章 並行プログラム
5
第7章 マクロ
5
第8章 マルチメソッド
5
第9章 現場のClojure
5

やっと、最後まで読めた。

5

じっくり読むために、1歳半の子ををいろんな施設にあづけている時間に読んだので、それにかかった金額は1万円くらいか。

5

それに比べると、本の購入金額なんてあって無いようなもんだな…

5

独身とか学生の方々は時間のある今のうちにいろいろ好きなことをしておく事をオススメする。

5

 

5

それはさておき、感想を。

5

ClojureはJVMと心中することで本気のプロジェクトにも使えるLispだと感じた。

5

SchemeやCommon Lispに慣れている人からすると、quoteやquasiquoteの記号が違っていたり、再帰のコーディングには気を付けないといけなかったりでちょっと勝手が違う所も有るだろうけど、やはりS色とマクロが揃っているので、本質部分はLispだ。

5

全体を通してClojureのデザインのうまい所は、参照透過な部分とそうでないコード(副作用を持つコード)を意識して分離できる仕組を全部そろえている所だと思う。

5

さほど意識しなくても純粋な部分を明確に意識して保護できる。(Haskellが純粋関数型言語ならClojureは高純度関数型言語か)

5

それを達成するために、処理系にはSTMやMVCCが組込まれていたり、データ構造の隅々までシーケンスで包みこんであったり、かなりの力が入っている。

5

やっぱり処理系がここまでしてくれれば、関数型プログラミングのメリットが十二分に発揮できるし、安心して関数型プログラミングができそうだ。

5

一般的にこの手のマイナー言語だが重要な書籍は原書(英語)でしか読めないところを、重要な本を日本語に翻訳して下さったshiroさんに感謝。

5

 

0

comment (disabled)

5

5

 

5

 

5

kiyoka.2010_02_03[本] プログラミングClojureを読む(その1)

5
 4274067890  プログラミングClojure: Stuart Halloway, 川合史朗
5

[目次]

5
第1章 Getting Started
5
第2章 Clojureひとめぐり
5
第3章 ClojureからJavaを使う
5
第4章 シーケンスを使ったデータの統合
5
第5章 関数型プログラミング
5
第6章 並行プログラム
5
第7章 マクロ
5
第8章 マルチメソッド
5
第9章 現場のClojure
5

 

5

第4章の途中を読んでいる所。

5

Nendoではどうするかなーと考えながら読んでいるので時間が掛かる。

5

特に、第3章で出てくるClojureからJavaへのアクセス構文については、Nendoでかなり参考にさせてもらっている。

5

本書を読みながら、改めて感じたことは、ClojureからJavaへのアクセスする構文のパターンが多すぎて複雑な気がする。

5

Nendoではあまりパターンを増やさず、一度チュートリアルを読めば覚えられる程度にとどめようと思った。

5

 

5

また読み進んだら、再度感想を書きます。

5

 

0

comment (disabled)

5