Archive for 2010年6月|Monthly archive page

何故、newLISP なのか?。。。または、多値を実装してみる

前にも書いていますが、改めて書いておきたいと思います。
折しも、newLISP の生みの親、Lutz Mueller 氏のインタビュー記事で次の言葉を読んで得心しました。

Less code, less errors, less memory, less CPU cycles, less energy, less resources.

newLISP のコンセプトは、この言葉につきますね。
さて、Lisp の優位性は、Paul Graham 氏が“On Lisp”や。“ハッカーと画家”で書いている通りだと思います。
そして、Lisp の代表格 Common Lisp でできることは、大抵 newLISP でもでき、優位性もそのまま当てはまります。
その確証として、“On newLISP” を書いてきました。いや、書いていく予定です。
かたや、300Kバイトに満たない実行ファイルに400弱の組込関数を持つ newLISP。
そして、Mバイトサイズの実行ファイルに1000個以上もある関数を持つ Common Lisp。
どちらを選びます?
私は、newLISP を選びました。私のやりたい事に、前述の newLISP のコンセプトが合っているから。
Common Lisp で、できないという訳ではないです。

要するに、私にとって重要なのは、Lisp なのか、Lisp でないのかではなく、
私がやりたい事に使えるか使えないのかなのです。

さて、Common Lisp と newLISP には、決定的な違いが三つあります。
細かいことを言えば、ぜんぜん違うものかもしれません。
“newLISP は Lisp ですらない”、という人もいるでしょう。
構いません(笑)、あくまでも私の見方です。

一つ目は、ドット対の有無。
二つ目は、レキシカル・スコープとダイナミック・スコープ。
三つ目は、戻り値に多値が使えるかどうか。

ドット対がどうしても必要で、どんな場合でもレキシカル・スコープ状態でなくてはならず、多値が使えなんて考えられない、という人は Common Lisp か Scheme ということに。もっとも、Scheme ですら Lisp でないという人もいるでしょうけど。

さて、ドット対がどうしても必要というのは、どういう状況でしょうか?
私の浅はかな考えでは、ドット対を前提に書かれたスクリプトがあるからしか思いつきません(汗)。
長年 Lisp や Scheme を使ってきた人にとってドット対のないことは、とんでもなことかもしれませんが、Lisp や Scheme を知らない人にとっては、どうでもよいことかもしれません。
ちなみに私は、Common Lisp から newLISP に乗り換えましたが、 newLISP の方が迷わずに済みました(笑)。
逆を言えば、Common Lisp を使っていた時は、ドット対には悩まされました(汗)。

二つ目、newLISP はダイナミック・スコープですが、context を使って部分的にレキシカル・スコープを実現できます。また、ダイナミック・スコープといっても、C 言語と同様に大域変数と局所変数は区別されます。
つまり、必要に応じてレキシカル・スコープを使うことができるということ。

三つ目、戻り値の多値。Lisp は、リストを戻り値にできるのに、何故多値が必要なのか?私には、判りません(汗)。ただ、関数truncate のように、ふつう必要なのは整数部分のみで、時々、切り捨てた小数部分を取り出すのに、別の関数を使わずに済むという意味では、使い易いのかもしれません。
リストの中身をインデックス機能で簡単参照できる newLISP では、ただリストを返せばよいだけのような気がしています。それで、前の“On newLISP”では、単にリストを返す仕様にしていました。
とはいえ、newLISP で多値が実装できないわけではありません。

(context 'MAIN:multiple-value-bind)
(define-macro (multiple-value-bind:multiple-value-bind)
  (letex (_var (args 0)
          _val (args 1)
          _body (cons 'begin (2 (args))))
	  (local _var
      (setq values:mv-set true)
      (map set '_var _val)
      (setq values:mv-set nil)
      _body)))

(context 'MAIN:values)
(define-macro (values:values)
  (letex (_item (args 0)
          _lst  (cons 'list (args)))
    (if mv-set _lst _item)))
(context MAIN)

前は実装しない、いや、できませんでしたが(汗)、今ならできます。
試しに、多値を返す関数truncate を用意して、

(define (truncate number)
  (let (num (if (< number 0) (ceil number) (floor number)))
    (values num (sub number num))))

multiple-value-bindを試すと、

> (multiple-value-bind (i f) (truncate 26.8175) (println i " "  f))
26 0.8175
0.8175
> (multiple-value-bind (i f) (values 1 2) (println i " "  f))
1 2
2

このように、values 単独でも使えます。
ちなみに、関数truncate は、

> (truncate 26.8175)
26

リストの第一項目のみを返しています。トップレベルで多値を返す、とまではいきませんが(汗)。
前述の関数truncate の使い方であれば、これで十分のような気がします。
ちなみに、多値の第二項目以降を見るには、multiple-value-list を使います。

> (multiple-value-list (truncate 26.8175))
(26 0.8175)

multiple-value-list の実装は、以下の通り

(context 'MAIN:multiple-value-list)
(define-macro (multiple-value-list:multiple-value-list)
  (letex (_val (args 0))
    (let (_lst)
      (setq values:mv-set true)
      (setq _lst _val)
      (setq values:mv-set nil)
      _lst)))
(context MAIN)

これで、newLISP でも、どうしても必要なら、多値が使えます。
私は、“On newLISP”以外で使うことは、まず無いでしょうね(笑)。

以上、如何でしょうか?

newLISP で On Lisp する...第2章(その4)

(この blog は、“short short story または 晴耕雨読な日々”からの引越してきたもの。スクリプトは、newLISP V10.2.1 以降で動作するように書き直しています。)
On Lisp 第2章 も その4 まで来てしまいました(汗)。
今回は、ローカル関数 からです。ここでのメインは、labels の紹介です。ところで、皆さんは、labels をお使いでしょうか? 恥ずかしながら、私はあまり使っていません。“mapcarの第1引数に再帰関数を与えたい時” は、defun で定義しています。また、“ローカルな束縛を必要とし、かつ再帰的な関数を mapcar に与えたい時” は、後述しますが、curry を使います。だから、あまり必要としなかったわけです。
前置きはさておき、newLISP には、labels はありません。
それで終わってしまっては、On newLISP にならないので、マクロを組みましょう。
newlisp-utility.lsp にも定義してあります。)

(define-macro (labels)
  (letex (_labels (append '(begin) 
                          (map (fn (x) 
                                 (list 'setq (x 0) (append '(fn) (list (x 1)) (2 x)))) 
                            (args 0))
                          (1 (args))))
    _labels))

え、定義できるの?と思われた方、私もそうでした。結論からすると定義できます
実際に動かしてみましょう。(i+ は、newlisp-utility.lsp に定義してあります)

> (labels ((incx (x) (i+ x))) (incx 3))
4 

newLISPには、組込関数inc がありますので、incx に変えてあります。また、1+ は i+ にしてあります。
では、関数に組み込んでも動くでしょうか。
見てみましょう。(defun は、newlisp-utility.lsp に定義してあります)

(defun count-instances (obj lsts)
  (labels ((instances-in (lst)
    (if (and (list? lst) (true? lst))
      (+ (if (= (car lst) obj) 1 0) (instances-in (cdr lst)))
    0)))
  (map instances-in lsts))) 

newLISP には consp がありませんので、list?true? を組み合わせて代用しています。もちろん、mapcar は map に変えてあります。 car と cdr もありませんが、newlisp-utility.lsp に定義してあります。使いたくなければ、それぞれを firstrest に書き換えてください。
では、動かしてみましょう。

> (count-instances 'a '((a b c) (d a r p a) (d a r) (a a)))
(1 2 1 2) 

所望の動作をします。ただし、newLISP は、ダイナミック・スコープですから、過信は禁物です。思わぬバグを引き起こすかもしれません。ご注意を。
newlisp-utility.lsp にある labels は、context 内で定義してあるのでより安全です。)
ここからちょっと番外です。関数count-instances のようなタイプを作りたい時は、私はこうしています。まず、内部の再帰関数を定義します。
(consp は、newlisp-utility.lsp に定義してあります。)

(defun instances-in (obj lst)
  (if (consp lst)
      (+ (if (= (lst 0) obj) 1 0) (instances-in obj (1 lst)))
    0)) 

当然、変数は2つ必要ですよね。これを map に与える際、次のようにします。

> (map (curry instances-in 'a) '((a b c) (d a r p a) (d a r) (a a)))
(1 2 1 2)

と、まあ、こんな感じで、newLISP で習い覚えた curry を使います。xyzzy用にも移植しました。ローカルな束縛が、第1引数だからいいものの、第2引数以降だったら? もちろん、大丈夫です。hayashi がありますから(笑)。ダイナミック・スコープの newLISP では、こちらの方が良いかも。とはいえ、labels を否定するものではありません。あれば便利です。ただ、便利だからといって、使うかどうかは別です。必要かどうかですよね。

さて、本題に戻りましょう。次は、末尾再帰 です。
まずは、末尾再帰でない例から、1+ は i+ にして、
(null は newlisp-utility.lsp に定義してあります)

(defun our-length (lst)
  (if (null lst)
      0
    (i+ (our-length (cdr lst))))) 

そして、末尾再帰の例、 fn は f にして、

(defun our-find-if (f lst)
  (if (f (car lst))
      (car lst)
    (our-find-if f (cdr lst)))) 

さらに、labels を使った例、

(defun our-length2 (lst)
  (labels ((rec (lst acc)
           (if (null lst)
               acc
               (rec (cdr lst) (i+ acc)))))
    (rec lst 0))) 

labels を定義していたので、順調に進みます。これでもよいのですが、私の場合は、以下のように書くことが多いです。

(defun our-length3 (lst (acc 0))
  (if (null lst)
      acc
    (our-length3 (1 lst) (i+ acc)))) 

CommonLisp でも第2引数に &opional を使えば、同じように書けます。では、何故 labels を使うのか?
それは置いといて、newLISP には、optimize や fixnum のような宣言はありません。
関数triangle は、以下のようになります。

(defun triangle (n)
  (labels ((tri (c n)
                (if (zerop n)
                    c
                    (tri (+ n c)
                         (- n 1)))))
    (tri 0 n)))

これで 末尾再帰 は終わりです。
私も、再帰関数は末尾再帰になるように心掛けてはいます。
でも、labels は使っていません。上記triangle は、私が書くとこうなります。

(defun triangle2 (n (c 0))
  (if (zerop n)
      c
    (triangle2 (- n 1) (+ n c)))) 

スクリプトの構造を見渡すには、labels を使った方が見やすいのかもしれません。特に、λ式を多用する人にとっては。そうでしょう?

さて、次は、コンパイル
残念ながら、ここでは、何もありません。newLISP には、コンパイラがありませんので。。。

そして、最後に、リストから作られる関数。ここでも、例題はありませんが、

“LispプログラムがLispプログラムを書けるということは,どんなに強調しても強調し過ぎにはならない.”

という言葉は、Lisp を newLISP に変えても当てはまるでしょう。
ちなみに、newLISP では、関数をリストとして扱えます。これも、newLISP の特徴です(笑)。

長かった第2章もようやく終わり、まとめです。

  • newLISP では、関数の定義には、define を使います。
    (補足::本 blog では、マクロで組んだ defun を使っています。)
  • newLISP では、λ式に、lambda も fn も使えます。
  • newLISP では、関数と変数の名前空間は同一です。したがって、#’ も funcall も要りません。
  • newLISP には、ドット対がありません。
  • newLISP には、属性がありません。
    ただし、context を使うことで、属性相当の記述は可能です。
  • newLISP は、ダイナミック・スコープです。
    ただし、context を使うことで、レキシカル・スコープ同等の記述は可能です。
  • newLISP でも、labels は実装できます。
  • newLISP には、コンパイル機能はありません。
    ただし、コンパイルしなくても高速です。また、単独で起動できる exeファイルを作ることができます。
  • newLISP では、関数をリストとして扱えます。

以上、如何でしょうか?

newLISP で On Lisp する...第2章(その3)

(この blog は、“short short story または 晴耕雨読な日々”からの引越してきたもの。スクリプトは、newLISP V10.2.1 以降で動作するように書き直しています。)
On Lisp 第2章 関数 も その3 まで来ました。
さて、今回は スコープ です。これも難題です。
Common Lisp は、レキシカル・スコープです。しかし、newLISP は、
(defun は newlisp-utility.lsp に定義してあります)

> [cmd]
(let ((y 7))
  (defun scope-test (x)
    (list x y)))
[/cmd]
(lambda (x) (list x y))
> (let ((y 5)) (scope-test 3))
(3 5) 

ご覧の通り、ダイナミック・スコープです。
この例で使っている [cmd] と [/cmd] は、端末(コンソール)で複数行を記述する時のタグです。
“On Lisp” によれば、レキシカル・スコープは、バグを回避する以上に、新しいプログラミング技法を可能にするもの。私もその意見に賛成です。
では、何故、ダイナミック・スコープの newLISP を選ぶのか?
その答えは、もちろん、context です。実際、そうなのか? 見ていきましょう。

> [cmd]
(context 'MAIN:scope-test)
  (setq y 7)
  (defun scope-test:scope-test (x)
    (list x y))
(context MAIN)
[/cmd]
scope-test
7
(lambda (x) (list x y))
MAIN
> (let ((y 5)) (scope-test 3))
(3 7)

このように、context を使えば、レキシカル・スコープ風の実装ができます。
この実装では、context の scope-test が、defun を囲っている let に相当します。
続いて、クロージャ。最初の例題は、さほど問題ありません。

(defun list+ (lst n)
  (map (fn (x) (+ x n))
       lst)) 

今まで見てきた変更点(mapcar は map、lambda は fn、#’ を外す)を適用しただけです。動作も同じです。

> (list+ '(1 2 3) 10)
(11 12 13) 

しかし、次の例題は、そのままでは使えません。レキシカル・スコープが前提ですから。
ここでも、context の登場です。

(context 'MAIN:id)
  (setq counter 0)
  (defun id:new () (++ counter))
  (defun id:reset () (setq counter 0))
(context MAIN)

動作はというと、

> (id:new)
1
> (id:new)
2
> (id:new)
3
> (id:reset)
0
> (id:new)
1
> (id:new)
2 

こんな感じです。
つまり、context を使うことで、レキシカル・スコープ同等の記述が可能になります。
関数名は違いますが、それは本質ではありません。もし、同じ関数名がお望みなら、

> (define reset-id id:reset)
(lambda () (setq id:counter 0))
> (define new-id id:new)
(lambda () (++ id:counter))
> (new-id)
3
> (new-id)
4
> (reset-id)
0
> (new-id)
1

こんな具合です(笑)。
さて、次の関数make-adder は、

(defun make-adder (n)
  (letex ((nn n))
    (lambda (x) (+ x nn)))) 

動作はというと、

> (setq add2 (make-adder 2) add10 (make-adder 10))
(lambda (x) (+ x 10))
> add2
(lambda (x) (+ x 2))
> (add2 5)
7
> (add10 3)
13 

もちろん、funcall は要りません。組込関数letex を使うことで、レキシカル・スコープでない点をカバーしています。実のところ、make-adder を作る手段はこの他にもあります
しかし、それでは、本章に似合いません。ここは context を使って、引数をローカルに捕捉して見せましょう。
(関数gensym は、Common Lispでは標準ですが、newLISP の組込関数ではありません。newlisp-utility.lsp に定義してあります。)

(defun make-adder (x)
  (let (ctx (gensym))
    (context ctx '_value x)
    (letex (y ctx)
      (fn (x) (+ x (y "value"))))))

関数gensym で生成したシンボル名の context に 変数_value を作成し、引数x を保存します。
変数_value と文字列 “value” の関係は、こちらこのあたりを参照して下さい。
では、実際の動作させて見ましょう。

> (setq add2 (make-adder 2) add10 (make-adder 10))
(lambda (x) (+ x (gensym12 "value")))
> add2
(lambda (x) (+ x (gensym11 "value")))
> (add2 5)
7
> (add10 3)
13

ざっと、こんなもんです。ここまで出来れば、次の内部状態を変化させられるクロージャも平気です。

(defun make-adderb (x)
  (let (ctx (gensym))
    (context ctx '_value x)
    (letex (y ctx)
      (fn (x change)
          (if change
              (y "value" x)
            (+ x (y "value")))))))

動作はというと、

> (setq addx (make-adderb 1))
(lambda (x change) 
 (if change 
  (gensym13 "value" x) 
  (+ x (gensym13 "value"))))
> (addx 3)
4
> (addx 100 t)
100
> (addx 3)
103

データオブジェクトを共有するクロージャのグループを返す関数make-dbms だって目じゃありません。

(defun make-dbms (db)
  (let (ctx (gensym))
    (context ctx ctx db)
    (letex (y ctx)
      (list
        (fn (key)
          (lookup key y))
        (fn (key val)
          (push (cons key val) y)
           key)
        (fn (key)
          (replace (assoc key y) y) key)))))

関数lookup は newLISP では組込関数です。また、newLISP の組込delete は Common Lisp のものと機能が違うので、組込replace に置き換えています。
動作はこうなります。

> (setq city '((boston us) (paris france)))
((boston us) (paris france))
> (setq citis (make-dbms city))
((lambda (key) (lookup key gensym18)) (lambda (key val) (push (cons key val) gensym18) 
  key) 
 (lambda (key) (replace (assoc key gensym18) gensym18) key))
> ((citis 0) 'boston)
us
> ((citis 1) 'london 'england)
london
> ((citis 0) 'london)
england
> ((citis 0) 'paris)
france
> ((citis 2) 'paris)
paris
> ((citis 0) 'paris)
nil

newLISP には、いわゆるドット対がありませんので、連想リスト表記が違います。ご注意を。
setf は使っていませんが、On Lisp の例題も、setf しなくもよいはず。使っている関数delete が、破壊的関数ですから。少なくとも、xyzzy ではそうです。
クロージャの呼び出しには、インデックス機能が使えます。こればっかりは、newLISP が有利ですね(笑)。
もちろん、

> ((car citis) 'boston)
us
> ((second citis) 'newyork 'us)
newyork
> ((car citis) 'newyork)
us
> ((third citis) 'newyork)
newyork
> ((car citis) 'newyork)
nil

という風にも使えます。
と言っても、Common Lisp の car は、newLISP の first で、second と third は、newLISP にありません。
car、second、third は、newlisp-utility.lsp に定義してあります。
またまた、長くなってしまったので、残りの ローカル関数 からは、次回に。
次回で、第2章も終わるでしょう(汗)。

以上、如何でしょうか?

newLISP で On Lisp する。。。第2章(その2)

(この blog は、“short short story または 晴耕雨読な日々”からの引越してきたもの。スクリプトは、newLISP V10.2.1 以降で動作するように書き直しています。)
前回に引き続いて On Lisp 第2章 関数
今回は、属性としての関数 からです。のっけから難物です(笑)。
case 文は、newLISPでも使えます。defun に newlisp-utility.lspのマクロを使えば、本文通り記述できます。

(defun behave (animal)
  (case animal
    (dog (wag-tail)
         (bark))
    (rat (scurry)
         (squeak))
    (cat (rub-legs)
         (scratch-carpet))))

試しに、wag-tail と bark を定義して試すと、

> (defun wag-tail ()  (print "wag-tail "))
(lambda () (print "wag-tail "))
> (defun bark ()  (print "bark "))
(lambda () (print "bark "))
> (behave 'dog)
wag-tail bark "bark " 

となります。ここまでは良いのですが、残念ながら、newLISPには属性はありません。したがって、属性を使った関数behave は、定義できません。
とはいっても、それで終わらないのが、newLISPです。
そう、newLISPには context があります。context を使えば、同様の実装ができます。

> (new 'Tree 'dog)
dog
> (dog "behavior" (fn () (wag-tail) (bark)))
(lambda () (wag-tail) (bark))
> (dog "behavior")
(lambda () (wag-tail) (bark)) 

前に紹介した context の使い方です。このように、関数もセットできます。前回見たように関数をデータオブジェクトとして扱えるのですから当然ですね。関数の呼び出しに、funcall は要りません。

> ((dog "behavior"))
wag-tail bark "bark " 

ということで、Common Lisp の属性を使った behave は、newLISP では context を使って、こうなります。

(defun behave (animal)
  ((animal "behavior")))

setf も使えます。

> (setf (dog "behavior") (fn () (wag-tail)))
(lambda () (wag-tail)) 
> (dog "behavior")
(lambda () (wag-tail)) 

context を予め定義する必要があるとか、キーとなるシンボルに "" を付ける(文字列として与える)必要があるとか、違いはありますが、気になるなら、マクロを組めばよいことですよね(笑)。
では、Common Lisp のように関数get を用意してみましょう。

(define-macro (get)
  (letex (_ctx (context (args 0))
          _key (term (args 1)))
    (_ctx _key)))

こんな感じでしょうか?
動作はというと

> (dog "behavior" (fn () (wag-tail) (bark)))
(lambda () (wag-tail) (bark))
> (get dog behavior)
(lambda () (wag-tail) (bark))

うまく行っているようです。クォートが付いていないことは、気にしないで下さい。後で説明します。
しかし、

> (setf (get dog behavior) (lambda () (wag-tail)))
(lambda () (wag-tail))
> (get dog behavior)
(lambda () (wag-tail) (bark))

setf でうまくセットされません。Common Lisp のマクロは、マクロ展開した後、マクロを呼び出したところに戻って評価されますが、newLISP の場合は、マクロ展開した後、マクロを呼び出したところの環境を使って、マクロ内で評価されます。この違いが、ここでは致命的です。
では、newLISP では、関数get はできない?結論からいうとできます。V10.1.6 から導入されたモジュール macro.lsp を使います。

(module "macro.lsp")
(macro (getm C K)
  (C (term K)))

これで、getm を定義して、試してみると、

> (get dog behavior)
(lambda () (wag-tail) (bark))
> (getm dog 'behavior)
(lambda () (wag-tail) (bark))
> (setf (getm dog 'behavior) (lambda () (wag-tail)))
(lambda () (wag-tail))
> (getm dog 'behavior)
(lambda () (wag-tail))
> (get dog behavior)
(lambda () (wag-tail))

この通り、setf も使えます。macro は、V10.1.6 から組み込まれた reader-event を使っていて、定義した内容を newLISP インタープリタがコードを評価する前に展開します。つまり、Common Lisp のマクロのように、展開式をマクロを呼び出したところに展開し、その後評価されるわけです。
これで、前述の関数behave も、

(defun behave (animal)
  ((getm animal 'behavior)))

と定義できます。

> (behave dog)
wag-tail "wag-tail "

動作もこの通り(笑)。さて、getm と baehave の第一引数にクォートがついていません。
これは第一引数が context だからです。
Common Lisp のようにクォートを付けた引数を取る get と behave の最終スクリプトは、

(macro (get C K)
  ((eval C) (term K)))
(defun behave (animal)
  (let (f (getm animal 'behavior))
    (if f (f) ((eval animal) "behavior" '()))))

となり、動作は、

> (get 'dog 'behavior)
nil
> (behave 'dog)
()
> (get 'dog 'behavior)
()
> (setf (get 'dog 'behavior) (fn () (wag-tail) (bark)))
(lambda () (wag-tail) (bark))
> (get 'dog 'behavior)
(lambda () (wag-tail) (bark))
> (behave 'dog)
wag-tail bark "bark "

こんな感じです。
関数behave のちょっとした細工は、context を予め定義しておかない時に発生するエラーの予防です。

さて、前の“On newLISP”では定義しなかった関数 get ができたところで、残り スコープ からは次回に。

以上、如何でしょうか?

newLISP で On Lisp する。。。第2章(その1)

newLISP で On Lisp する。。。第2章(その1)
(この blog は、“short short story または 晴耕雨読な日々”からの引越してきたもの。スクリプトは、newLISP V10.2.1 以降で動作するように書き直しています。)
第1章は如何だったでしょうか? さて、今回は、第2章 関数です。
newLISP も Common Lisp 同様、関数型の言語です。しかし、関数の定義には、defun ではなく define を使います。

> (define (double x) (* 2 x))
(lambda (x) (* 2 x))
> (double 1)
2 

もっとも、defun をマクロで組めば、一見、Common Lisp 風に見えます。
(defun は、newlisp-utility.lspでマクロ定義してあります。)

> (load "newLISP-utility.lsp")
:
> (defun double (x) (* 2 x))
(lambda (x) (* 2 x))
> (double 1)
2

しかし、newLISP は Common Lisp と違って、変数と関数に異なった名前空間はありません。つまり、同じ名前に同時に変数と関数を与えることは出来ません。そのおかげ(?)で、#’(シャープ・クォート)オペレータが要りません。というよりありません。先ほど定義した関数double は、単体で

> double
(lambda (x) (* 2 x)) 

となり、関数としてのオブジェクトが現れます。double が変数で値が入っていれば、その値が現れることになります。もちろん、関数は、λ式で表すことが出来ます。しかも、lambda の代わりに fn が使えます。

> (fn (x) (* x 2))
(lambda (x) (* x 2)) 

そして、

> (double 3)
6
> ((fn (x) (* x 2)) 3)
6 

と、まあ、ここまでは同じですが、前述のように、関数と変数で名前空間が同じですから、シンボルdobule に関数と値を同時に持たせることは出来ません。もちろん、symbol-value なんかもありません。
とはいえ、関数を変数に入れられるという点では同じです。

> (setq x double)
(lambda (x) (* x 2))
> (x 2)
4
>(= x double)
true

#’ も symbol-function も要りませんが、x に double の関数オブジェクトが入っています。
setf も使えます。

> (setf double (fn (x) (* x 2)))
(lambda (x) (* x 2))

当然、関数を引数にも出来ます。

> (+ 1 2)
3
> (apply + '(1 2))
3
> (apply (fn (x y) (+ x y)) '(1 2))
3

くどいですが、#’ も symbol-function も要りません。ありませんから(笑)。
しかし、newLISPでは、

(apply #'+ 1 '(2))
(funcall #'+ 1 2)

はできません。前者は、apply の引数のとり方の違いから、、、
でも、こういう事はできます。

> (apply (curry + 1) '(2))
3

newLISP組込curry は、とても便利で重宝します。
膨大な組込関数も持つ Common Lisp に無いのが不思議なくらいです。
と言っても、マクロで簡単に組めますけどね。
後者は、funcall 自体がない、というより必要がない。
map 系関数も当然あります。といっても、map だけですけど。関数の機能自体は、mapcar と同じです。Common Lisp の map とは、異なります。

> (map (fn (x) (+ x 10)) '(1 2 3))
(11 12 13)
> (map + '(1 2 3) '(10 100 1000))
(11 102 1003)

sort もあります。 当然、#’ は要りません。

> (sort '(1 4 2 5 6 7 3) <)
(1 2 3 4 5 6 7)

 さて、remove-if ですが、newLISP には、ありません? 嘘です、あります。名前が違っているだけです。組込関数clean がそれにあたります。ちなみに、remove-if-not は、組込関数filter に。でも、何故か evenp はありません。定義します。

> (defun evenp (x) (= (& x 1) 0))
(lambda (x) (= (& x 1) 0))
> (define remove-if clean)
clean 
> (remove-if evenp '(1 2 3 4 5 6 7))
(1 3 5 7)

とまあ、こんな感じです。私がマクロを使ってまで、関数定義に defun を使っているのは、上記のように、define は C言語の #define のようにと使い分けたいからです。
さて、our-remove-if の定義ですが、拙作newLISP用ユーティリティ(car、cdr、defun、null が定義してあります)を使えば、 fn を f に置き換え、funcall を外すだけです。

(defun our-remove-if (f lst)
  (if (null lst)
      nil
    (if (f (car lst))
        (our-remove-if f (cdr lst))
      (cons (car lst) (our-remove-if f (cdr lst))))))

早速試してみると、

> (our-remove-if evenp '(1 2 3 4 5 6 7))
(1 3 5 7 nil)

あらら、nil がくっついている。これは、newLISP の仕様なのでしかたありません。あらためて、newLISP用を定義すると、こうなります。

(defun our-remove-if (f lst)
  (if (null lst)
      '()
    (if (f (car lst))
        (our-remove-if f (cdr lst))
      (cons (car lst) (our-remove-if f (cdr lst))))))

なんのことはない、nil を空リストに変えただけです(笑)。

> (our-remove-if evenp '(1 2 3 4 5 6 7))
(1 3 5 7 nil)

ついでに、newLISP だけで記述してみましょう。

(define (our-remove-if f lst)
  (if (not lst)
      '()
    (if (f (first lst))
        (our-remove-if f (rest lst))
      (cons (first lst) (our-remove-if f (rest lst))))))

と、こんな感じ、如何でしょうか?

長くなったので、第2章の残りは次回に。

newLISP で On Lisp する。。。第1章

(この blog は、“short short story または 晴耕雨読な日々”からの引越してきたもの。スクリプトは、newLISP V10.2.1 以降で動作するように書き直しています。)
(”On newLISP” は、“On Lisp”のスクリプトを newLISP で実装してみようという、試みです。経緯は、こちらでどうぞ。)

On Lisp” は説明するまでもなく、Paul Graham氏の名著です。私はこれを読んで Lisp を始めました。私が持っているのは、野田開氏訳の日本語翻訳本(ISBN978-4-274-06637-5)です。Web上で見ることが出来ますが、願わくば、購入されますように。それだけの価値はあります。

では、“第1章 拡張可能なプログラミング言語”から。特に断らない限り、CommonLisp の動作は、xyzzy での動作です。
“Lispの拡張” にあるスクリプトは、Common Lispにとっては、特に目新しいスクリプトはありません。

; スクリプト1
(mapcar fn
        (do* ((x 1 (1+ x))
              (result (list x) (push x result)))
          ((= x 10) (nreverse result))))

しかし、newLISP にとっては、厄介なスクリプトです。do* がありませんからね。いきなり大物マクロを組まなくてはいけません。
その前に、注意点。newLISP では、fn は組込関数です。Arc のように、lambda と同じ機能です。newLISP では、mapcar は map に、nreverse は reverse に相当します。関数1+ もありませんので、関数i+ を用意します。’i+’ なのは、関数や変数に ‘1+’ という名前が使えないこと。これは仕様なのでしかたありません。
前置きはさておき、do* の実装です。xyzzy reference.chm の do* 項を参考に作りました。

; スクリプト2
(define-macro (do*)
  (letex (_init (map (hayashi slice 0 2) (args 0))
          _steps (cons 'setq (flat1 (map (hayashi select '(0 2)) (args 0))))
          _results (cons 'begin (rest (args 1)))
          _end-test (first (args 1))
          _body (cons 'begin (2 (args))))
   (letn _init
     (until _end-test
       _body
       _steps)
     _results)))

中ほどの letn は、Common Lisp では、let* に相当します。これを let に変え、newLISP 版 psetq を用意すれば、do のマクロになります。その、実装例は、こちらにありますhayashiflat1 は拙作です。newlisp-utility.lsp に定義してあります。
先ほどの、注意点を含めスクリプト1を newLISP 用に書き直すと、

; スクリプト3
(define mapcar map)
(define nreverse reverse)
(define (i+ x) (+ 1 x))
(mapcar fn
        (do* ((x 1 (i+ x))
              (result (list x) (push x result)))
          ((= x 10) (nreverse result))))

これで、動きます。
(i+ は、newlisp-utility.lspで macro定義してあります。newlisp-utility.lsp を先にロードしている場合は、i+ の定義をコメント・アウトして下さい。)
実際動作させてみると、

> [cmd]
(define mapcar map)
(define nreverse reverse)
(define (i+ x) (+ 1 x))
(mapcar (fn (x) (* 2 x))
        (do* ((x 1 (i+ x))
              (result (list x) (push x result)))
          ((= x 10) (nreverse result))))
[/cmd]
(2 4 6 8 10 12 14 16 18 20)
>

さて残り、map1-n は、第4章で実装する予定です。
for は、newLISP 標準関数なので、実装しないかも(笑)。
実のところ、数字の 1 から 10 までのリストを作るだけなら、newLISP では 組込sequence を使えば、一発で済みます。

> (sequence 1 10)
(1 2 3 4 5 6 7 8 9 10)

つまり、map1-n の実装は超簡単(笑)。
ということで、今日のまとめです。

一番の問題は、最後の項目でしょうか。私にとっては、致命的でないですけどね。
さて、newLISP が、第1章のタイトル “拡張可能なプログラミング言語” であることは、間違いのないところ。そして、章の最後の問いは、“いつ Lispか?” 。ということで、この blog の締めには、次の問いを。

いつ newLISPか?

私のお薦めは、こうです。

newLISP V.10.2.8 から

以上、如何でしょうか?

GUI で midi する。。。または、キーボードを鍵盤に(midi編)

GUI で midi する。。。または、キーボードを鍵盤に” の今回の解説は、midi 関連関数 gs:midi-xxx 部分です。
と言っても、使っているのは、次の三つです。

(gs:midi-init)
(gs:midi-patch str-instrument int-channel)
(gs:play-note int-key int-duration int-velocity int-channel)


gs:midi-init
が、midi関連関数の初期化で、必ず一回は必要です。
gs:midi-patch で楽器を選択します。

(gs:midi-init)
(gs:midi-patch "Piano" 0)

引数は、説明するまでも無い? 使える楽器は、gs:get-instruments で取得できます。
後は、 gs:play-note を使って音を鳴らします。

; for key-event
(gs:play-note (+ (key 1) *plusNote*) 2 95 0)

; for mouse-event
(gs:play-note (+ (lookup (sym (tags 0)) *allNote*) *plusNote*) 2 95 0)


gs:play-note
の引数は、順に音階、音の長さ、音の強さ、(楽器に割り当てた)チャンネルとなります。
音階は、60 がいわゆるドの音で、ドレミファは 60 62 64 65 となります。つまり、1 が半音に相当します。1オクターブは、12 です。
今回のスクリプトでは、大域変数 *allNote* に音階名(Do Re Mi …)と音階の数値(60 62 64…)の連想リスト(…(Do 60) (Re 62) (Mi 64) …)が入っています。
さらに、鍵盤画像の鍵盤に音階名でタグ付けしてあります。
そのため、マウスで鍵盤をクリックすると、マウス・イベントで音階名のタグが得られ、それを使って、連想リストから、音階値を lookup します。
また、大域変数 *allNote* には、さらに、文字コードとの連想リスト(…(83 (Do 60)) (68 (Re 62)) (70 (Mi 64)) …)にしています。
この連想リストから押されたキーで lookup して、音階値を得ています。
変数 *plusNote* は、SHIFT キーが押されたか、CTRL キーが押されたかで、+12 か -12 が入ります。
音の長さは、16が四分音符相当です。つまり1は、64分音符、全音符は、64です。
今回は使っていませんが、テンポは、gs:midi-bpm で設定し、デフォルトは、120です。
音の強さは、0から127までが使え、デフォルトが 64 です。
newLISP の Domo Folder(newLISPインストール・ディレクトリ下の ‘guiserver’)にある‘midi-demo2.lsp’では、

(set 'pp 30 'p 40 'm 64 'f 127) ; set velocity/volume

という風に設定しています。

ということで、今回の解説を読むよりは、前述の‘midi-demo.lsp’や‘midi-demo.lsp’を読んだ方が早いかもしれません(汗)。

以上、如何でしょうか?

GUI で midi する。。。または、キーボードを鍵盤に(解説編)

前回の “GUI で midi する。。。または、キーボードを鍵盤に” は如何だったでしょうか?

さて、解説と言っても、短いスクリプなので解説もいらないかもしれません。
今回の目玉は、gs:key-event と midi 関連関数 gs:midi-xxx だと思います。
まずは、gs:key-event から。
この関数で指定したハンドラ関数 key-action が取る引数は、4つあります。

(key-action id type code modifiers)

この内、id はおなじみ、イベント発生場所のコンポーネントID です。
type にはキーが押されたか離れたかで文字列 “pressed” か “released” が入ります。
code はキーコードで、大抵は ASCII コードですが、一部違うものがあります。前回紹介したスクリプトは日本語106用です。他のキーボードでは、動かないキーが有るかもしれません。関数key-action の一行目のコメント・アウトを外して確認して下さい。
modifiers には、SHIFT キーや CTRL キーの状態が入っています。ビット0 が SHIFTキー、ビット1 が CTR Lキーで、それぞれ押されると 1 が入り、押されていなければ、0 が入ります。
参考までに、modifiers の解析用関数を作ってみました。

(define (parse-modifiers n)
  (1 (map int (explode (bits (+ 32 n))))))
(define (translate-modifiers n)
  (let (syms '(left-click middle-click right-click ctrl shift)
        mods  (replace 0 (parse-modifiers n) nil))
    (replace nil (map and mods syms))))

実行すると、

> (parse-modifiers 17)
(1 0 0 0 1)
> (translate-modifiers 17)
(left-click shift)

こんな感じです。この引数には、SHIFT キーや CTRL キーの他にマウス・ボタンの動作も入って、マウス・イベント関数mouse-action でも使われています。
閑話休題、gs:key-event に戻りましょう。
この関数は、

(gs:key-event 'OutputArea 'key-action)

このように、ハンドラが動作するコンポーネントを指定します。
つまり、このコンポーネントがアクティブ時のみイベントが起こります。
これが曲者で、gs:listen ではうまく動かないのです。
gs:check-event でループをまわし、gs:request-focus を使って強制的にコンポーネントをアクティブにすることが必要です。

(while (gs:check-event 10000)
   (gs:request-focus 'OutputArea) ; keep focus in canvas
)

これが gs:key-event を使う時の肝でした。
さて、今回はこれくらいにして、残り midi 関連は、次回に。

以上、如何でしょうか?

GUI で midi する。。。または、キーボードを鍵盤に

newLISP-GS には、midi 用の関数があります。
それを動かすには、newLISP と Java だけでなく soundbank が必要です。
具体的には、Java のインストール・ディレクトリ下の ‘/lib/audio’ (Windows では、’\lib\audio’)に ‘soundbank.gm’ があるかどうかです。なけれな、ダウンロードして所定の場所に置いて下さい。newLISP-GS の マニュアルでは、mid 以上が推奨されています。
newLISP の Domo Folder(newLISPインストール・ディレクトリ下の ‘guiserver’) にある ‘midi-demo.lsp’ を起動して音が鳴れば、OKです。
そして、今回のスクリプトは、

; utility
(define *newlispDir* (env "NEWLISPDIR"))
(load (append *newlispDir* "/guiserver.lsp"))
; define assoc-list of note
(define *plusNote* 0)
(define *wNote* '(si Do Re Mi Fa So La Si DO RE MI FA))
(define ABC   '(59 60 62 64 65 67 69 71 72 74 76 77))
(setq *allNote* (map list *wNote* ABC))
(define keycode (append (map char (explode "ASDFGHJKL;")) (list 513 (char "]"))))
(define *bNote* '(la# nil Do# Re# nil Fa# So# La# nil DO# RE#))
(define ABC   '(58      61  63      66  68  70      73 75))
(extend *allNote* (map list (replace nil (copy *bNote*)) ABC))
(extend keycode (map char (explode "QERYUIP")) (list 512))
(setq *key-note* (transpose (list keycode *allNote*)))
; define handler
(define KW 50)
(define KH 200)
(define (draw-wnote key revflag , i)
  (when (setq i (find key *wNote*))
    (if revflag
        (gs:fill-rect key (+ (* KW i)) 0 KW KH gs:lightGray)
      (gs:draw-rect key (+ (* KW i)) 0 KW KH gs:black))))
(define (draw-bnote key revflag , i)
  (when (setq i (find key *bNote*))
    (if revflag
        (gs:fill-rect key (- (* KW i) (/ KW 2)) 0 KW (/ (* KH 2) 3) gs:lightGray)
      (gs:fill-rect key (- (* KW i) (/ KW 2)) 0 KW (/ (* KH 2) 3) gs:black))
    (gs:draw-rect key (- (* KW i) (/ KW 2)) 0 KW (/ (* KH 2) 3) gs:white)))
(define (key-action id type code modifiers)
  ;(println "id:" id " type:" type " key code:" code " modifiers:" modifiers)
  (let (key (lookup code *key-note*)
        lst '(0 12 -12))
    (if key
        (case type
          ("pressed"  (gs:play-note (+ (key 1) *plusNote*) 2 95 0)
                      (if-not (draw-wnote (key 0) true)
                        (draw-bnote (key 0) true))
                      (gs:update))
          ("released" (gs:delete-tag (key 0))
                      (if-not (draw-wnote (key 0))
                        (draw-bnote (key 0)))
                      (gs:update))
           (true ))
        (case (& modifiers 3)
          (1 (setq *plusNote* (lst 1))) ; shift key
          (2 (setq *plusNote* (lst 2))) ; ctrl  key
          (true (setq *plusNote* (lst 0)))) ; other
  )))
(define (mouse-action x y button cnt mods taglst)
  ;(println " x:" x " y:" y " button:" button " count:" cnt " mods:" mods " tag:" taglst)
  (if (and taglst (not (ends-with (taglst 0) "TEXT")))
      (let (tags (replace "MAIN" (flat (map parse taglst))))
        (if (or (= (length tags) 1) (ends-with (tags 0) "#"))
            (gs:play-note (+ (lookup (sym (tags 0)) *allNote*) *plusNote*) 2 95 0)
          (gs:play-note (+ (lookup (sym (tags 1)) *allNote*) *plusNote*) 2 95 0)))))
; initialization
(define FPosX 50)
(define FPosY 50)
(define FWidth 583)
(define FHeight 300)
(gs:init)
(gs:midi-init)
(gs:midi-patch "Piano" 0)
(gs:frame 'Frame FPosX FPosY FWidth FHeight "KeyBoard Smaple")
(gs:panel 'StatusPanel)
(gs:canvas 'OutputArea)
(gs:set-color 'OutputArea gs:white)
(dolist (s *wNote*) (draw-wnote s))
(dolist (s *bNote*) (and s (draw-bnote s)))
(gs:set-font 'OutputArea "Monospaced" KW)
(gs:draw-text 'TEXT "A S D F G H J K L ; : ]" (* (/ KW 4) 1) (+ KH KW))
(gs:key-event 'OutputArea 'key-action)
(gs:mouse-clicked 'OutputArea 'mouse-action true)
; mount all on frame
(gs:set-border-layout 'Frame)
(gs:set-flow-layout 'StatusPanel "left")
(gs:add-to 'Frame  'OutputArea "center" 'StatusPanel "south")
(gs:set-visible 'Frame true)
; main routine
;(gs:listen)
(while (gs:check-event 10000)
   (gs:request-focus 'OutputArea) ; keep focus in canvas
)
(exit)

こんな感じ。実行すると、

という風に、鍵盤が表示され、キーボードのS、D、F を順に押していくと、ドレミと鳴ります。
後は、ご想像通りです(笑)。お試しあれ。
キーボードをフルに使えば、二列二段で音階を増やせますが、タイトルに有るようにサンプルです。適当に改良して下さい。これで演奏したい人は、いないでしょう?(笑)
マウスで鍵盤をクリックしても鳴ります。
上記画像は、WindowsXPの場合です。他のプラットフォームでは、文字表示の位置がずれているかもしれません。
gs:draw-text

(gs:draw-text 'TEXT "A S D F G H J K L ; : ]" (* (/ KW 4) 1) (+ KH KW))

の Y 値を適当な値に変更して下さい。
それ以外の解説は、次回に。

以上、如何でしょうか?

ブログ開始にあたって

newLISP は、いわゆる LISP の方言の一種です。
newLISP と他の LISP との違いは、”Comparison to Common Lisp and Scheme” にあります。
日本語で読みたい方は、KOSH さんの“舌足らずなブログ”にあります。こちらからどうぞ。

私は、Paul Graham 氏の “On Lisp” (もちろん翻訳本)を読んで、Lisp を始めました。
そして、newLISP に出会い、本格的にスクリプトを書き始め、ブログ“short short story または 晴耕雨読な日々”にカテゴリ “LISP” と “On newLISP” で投稿してきました。

しかし、newLISP 関する部分を忘備録として使い易そうなここ(Word Press)で書くことにしました。
書きためたスクリプトや newLISP マニュアルの翻訳(現在、guiserver を翻訳中)等を紹介していく予定です。
また、newLISP のバージョンアップに合わせた “On newLISP” の書き直しも予定しています。

以上、如何でしょうか?