Hatena::ブログ(Diary)

分室の分室 このページをアンテナに追加

2016-08-25 Thu

eval 使ってみた (eval で apply を再現)

【565】

ちょいネタ。

 適当に組み立てた『戻り値』をコードとして実行する時に eval は便利。でも、たぶん Scheme で eval を使うシーンはほとんど無いと思う。apply が eval の代用として活躍するので。eval と apply には類似性がある気がしたので、eval で apply を再現することを思いついた。

eval の使い方(Gauche用。たぶん TinySchemeでも実行可能(動作未確認)):

 gosh> (eval "(max 1 2 3 4)" (interaction-environment))
 "(max 1 2 3 4)"
 gosh> (eval '(max 1 2 3 4) (interaction-environment))
 4
 gosh> (eval (cons + '(1 2 3 4)) (interaction-environment))
 10
 gosh> (cons + '(1 2 3 4))
 (#<subr +> 1 2 3 4)
 gosh> (apply + '(1 2 3 4))
 10

 ご覧のとおり、Scheme の eval は "文字列" ではなく、クォートでエスケープしたリスト、もしくはプロシージャの戻り値に対して働くらしい。要するに戻り値に対して働く。上記の実行例を見れば apply と eval がなんとなく似ているのが、なんとなく分かると思う。なんとなく。

apply の書式:(apply proc arg1 arg2... (argx argy argz...))
        条件:引数の最後はリストでなければならない
              (リストは絶対に必要:省略不可)

apply の挙動:上記書式の引数 arg1 〜 argz を proc の引数として渡して、
              proc を実行し、その戻り値を返す。最後のリストは中の要素
              を取り出して引数として渡す。つまり、proc は次のような形
              で実行される。↓

              (proc arg1 arg2... argx argy argz...)

        文例:(apply - 20 10 '(5 1)) → (- 20 10 5 1) を実行
              ⇒ 4 を返す

 apply を再現するにあたって、引数の順番は変わってはいけないことが文例からも分かると思う。例えば、再現後の引数が逆順になったら、上記文例の戻り値は負数になってしまう(問題の意味が根底から変わってしまうのはダメってことで)。

 eval を使うのなら、apply の再現は非常に簡単。cons を使ってリストを作り直すだけで済むので。引数の最後がリスト決め射ちになってるのもありがたい仕様。ちなみに、eval を使わずに apply を定義する方法は、自分には全く想像できなかった。。。

;; eval で apply を再現
;; 単純再帰で cons するのが一番簡単かなぁ…
;; 引数:args はリストで格納される → (p val1 val2... (valx valy...))
;; 備考:再帰の終了条件(基底条件)での args 末尾の elm はリスト
;;       (apply の引数末尾はリストなので)
(define my-apply
  (lambda args
    (let ((remaked-list
            (let recursion ((elm (car args)) (ls (cdr args)))
              (if (null? ls)
                elm
                (cons elm (recursion (car ls) (cdr ls))) ))))
      (eval remaked-list (interaction-environment)) )))

;実行例:
;    gosh> (my-apply + '(1 2 3 4))
;    10
;    gosh> (my-apply + 5 5 '(1 2 3 4))
;    20
;    gosh> (my-apply max 5 0 '(1 2 3 4))
;    5
;    gosh> (my-apply min 5 0 '(1 2 3 4))
;    0
;    gosh> (my-apply - 50 10 '(1 2 3 4))
;    30

 前回やった (lambda args exp...) の書式をさっそく使ってみた。今までなら (lambda (p . args) exp...) の書式を使って (all (cons p args)) とかやったと思う。が、p を特別視する必要が無いのなら、こっちの書式を使うべきかな…やっぱり。この書式を使ってみたくて今回のネタを考えた…と、言えなくもない。。。

 そんなワケで my-apply の実用性は間違いなくゼロだが、適当に組み立てたリストを手軽に実行させたい時に、eval を使いたいことも有るかもしれない(無いかもしれない…)。

2016-08-24 Wed

謎の lambda 式…とりあえず書式だけ押さえておく

【564】

追記アリ:

 正直なところ自分は、ラムダ式、ラムダ計算というものがさっぱり理解できない。分かっているのは「ここをこうすると、何故かこうなる」という原因と結果の関係…因果律の流れのごく一部を何となく把握しているだけ…。

 おそらくこれは『数学者のジャンル』に該当するものだと感じている。なので深入りするつもりは全くない。


lambda の書式を覚える:

 (lambda はプロシージャを生成して返す)
 (ここをこうすると、何故かこうなる)
 1ユーザーとしては、これだけ知っていれば充分かなー、と。

;●プロシージャの定義:
    (lambda () 式...)         ;←仮引数を省略すると begin になる?
    (lambda (x) 式...)
    (lambda (x y) 式...)
    (lambda (x . y) 式...)
    (lambda (x y . z) 式...)  ;…ここまでは、まあ分かる(ことにしておく)
    (lambda x 式...)          ;これは何? (これを理解するのが今回の目的)

;●プロシージャの実行(プロシージャを変数に格納して実行):
;  (手続型言語の無名サブルーチンの実行と同じ気がする…)
    (define proc (lambda (x) 式...))
    (proc a)                  ;仮引数 xyz, 実引数 abc で表現(以下同)

    (let ((proc (lambda (x . y) 式...)))
      (proc a b c))           ;…ここまでは、まあ分かる(ことにしておく)

    (define proc (lambda x 式...))
    (proc a b c)              ;この書式は 0個以上(複数)の引数を指定可能
    (proc)                    ;引数を省略しても OK

;●プロシージャを定義して直ちに実行(クロージャ):
    ((lambda () 式...))       ;戻り値(プロシージャ)が外側のリストに渡る
    ((lambda (x) 式...) a)
    ((lambda (x y) 式...) a b)
    ((lambda (x . y) 式...) a [b c ...])
    ((lambda (x y . z) 式...) a b [c d ...])
    ((lambda x 式...) [a b c d ...])    ;←この書式の理解が今回の目的
    ;(リストの先頭はプロシージャと解釈されるので、これだけで実行可能)

;●クロージャの書式(基本形):
    ((lambda (仮引数...) 式...) 実引数...)
    ;…ここまでは、まあ分かる(ことにしておく)

; このソースは模式図なので実行不可
; )((pre要素で『はてな注釈記法』をエスケープするのが面倒だったの…))(

 いきなりクロージャが出てくるのが Scheme のキツイところ。だが、過剰に怯える必要はないらしい。手続き型言語でクロージャと言えば無名サブルーチン(無名関数)を指す。ところが Scheme のクロージャは、サブルーチンというよりも『局所化されたブロック』としての意味合いが強いらしい(処理系による?)。(手続き型言語での通常のブロックは局所化のパーティションとして働く)

 言われてみれば確かに、lambda 式の一番単純な形は、よく見れば begin と等価になっている。↓

 (begin
    some-statement-1
    some-statement-2 ...)

 ((lambda ()
    some-statement-1
    some-statement-2 ...))

 これは要するに通常のブロックとほぼ等価だ↓

 # 手続型言語のブロック構造
 {
    some_statement_1;
    some_statement_2;
 }

 追記:手続き型言語のブロックとの違いは、このブロックは『戻り値を返す』ということ。

 とりあえず、ここまでは分かった(ことにしておく)。要は目的どおりのコードが作れればそれでいい。問題はここから……

;●プロシージャの定義:
    (lambda x x)        ;これは何 ?

 これをどう解釈すれば良いのか全く想像がつかなかったが、ネットで分かり易い説明を見つけた(引用させていただきました)。↓

●可変個引数

Scheme の場合、ラムダ式の仮引数は次に示す 3 通りのパターンがあります。

  1.  (lambda (a b c) ... )
  2.  (lambda (a b c . args) ... )
  3.  (lambda args ... )

1 は…(中略)…ができます。3 のように変数 args だけの場合、与えられた引数すべてがリストに格納されて args に渡されます。引数がない場合、args は空リストになります。つまり、0 個以上の引数を受け取る関数になります。

【参考】Scheme プログラミング中級編(その4)
        http://www.geocities.jp/m_hiroi/func/abcscm14.html
        ●可変個引数
        ( M.Hiroi's Home Page:http://www.geocities.jp/m_hiroi/ )

 このサイトはけっこう見に来てるんだけど、このページまで読み進んだのは、今まではなかった気がする。

 今までは (lambda x x) の書式の実行結果からの規則性・法則性が見いだせなくてずっと避けてきたが、これでようやく lambda の 3パターンの書式が一応理解できた。最後の書式をまとめると次のようになるのかな。↓

;●プロシージャの定義例:
    (lambda x x)                ;最初の x が仮引数、次の x は式
    (lambda args (list args))   ;つまり、こんな記述ができる

;●クロージャの書式(上記の場合):
    ((lambda 仮引数 式...) 実引数...) ;⇒ 最後の式の結果を返す
                                      ;(引数はリストとして扱われる)
;●クロージャの実行例:
;    gosh> ((lambda x))     ;式と実引数を指定してないので無意味…
;    #<undef>               ;(未規定値を返す手続という意味はある)
;    gosh> ((lambda x x))   ;実引数を指定しない場合は () となる
;    ()                     ;(空リストを返す begin みたいな?)
;    gosh> ((lambda ls (list ls)) 1 2 3)
;    ((1 2 3))              ;ls は元々リストなので ⇒ 2重リストを返す
;    gosh> ((lambda args args) 1 2 3)
;    (1 2 3)                ;(list 1 2 3) と等価
;    gosh> (define y 999)
;    y
;    gosh> ((lambda x (list y x)) 1 2 3)
;    (999 (1 2 3))
;    gosh> ((lambda x (cons y x)) 1 2 3)
;    (999 1 2 3)

 gosh> (let ((f (lambda x x))) (f 'a))
 (a)
 gosh> (let ((f (lambda x x))) (f 'a '(b c) 'd))
 (a (b c) d)
 gosh> (define z 111)
 z
 gosh> ((lambda x x y z) 1 2 3)       ;最後の式 z の結果が戻り値
 111                                  ;(これも begin ぽい)
 gosh> ((lambda x (display 'a) x))    ;副作用の後で () を返す
 a()                                  ;(これも begin ぽい)
 gosh> ((lambda z) display "abcd\n")  ;z は仮引数でもあり式でもある(?)*1
(#<subr display> "abcd\n") ;*1 gosh> ((lambda z z) display "abcd\n") (#<subr display> "abcd\n") gosh> ((lambda z (car z)) display "abcd\n") #<subr display> gosh> z 111

 この ((lambda 仮引数 式...) 実引数...) という書式を使った定義例は、自分はあまり見たことがない(気がする)が『実引数がリストとして扱われる』のは、いつか何かに使えそうな予感はある。とりあえず lisp 系言語としては非常に重要な書式なんだろうな、ということは想像が付く((lambda x x) は手続き list と等価)。

 実行結果に対して矛盾のない説明が一応できるようになったので(あくまで一応…)、今回の記事は 1表現者としては非常に意義があった(長文の割には地味な内容ではあるが)。何よりも、自分にとって分かり易い文章がネットに掲載されていたのが、理解を深める助けになった。

追記2:2016.08.25 Thu 15:00 よく考えたら、
(define (proc . args) exp...)       …と、
(define proc (lambda args exp...))  …は、同じ意味だった。↓

;    gosh> (define (p1 . args) args)
;    p1
;    gosh> (define p2 (lambda args args))
;    p2
;    gosh> (p1 1 2 3)
;    (1 2 3)
;    gosh> (p2 1 2 3)
;    (1 2 3)

糖衣構文で、気付かずに使っていた(ちゃんちゃん)。。。

*1:1か所 (?)*1 …の所だけ説明出来てない気がするが…こんな記述はしないと思うので、まあいいか。