はじめに
前回(「LIPS Schemeを使ってみました2)に引き続きLipsの記事です。

今回は、position-ifを学習しました!。 v( ̄Д ̄)v イエイ
しかしschemeはposition-ifを標準サポートしてませんでした。(´・ω`・)エッ?
いつものようにAI先生にシンプルな実装サンプルを提示してもらいました。(´▽`)アリガト!
今回習得した技は次の3つです。
- 名前付きletループ(named let loop)構文
- 可変長引数
- キーワード風引数
動作確認はいつものLipsサイトを使いました。
相変わらずlispの慣例的な書式(特に閉じカッコの位置)に不慣れで馴染みのある手続き型プログラム風に書いています。(´Д`υ)))ポリポリ
実装するposision-ifについて
position-ifは、シーケンス(リストなど)の中から、指定した述語(テスト条件)を満たす最初の要素のインデックス(位置)を検索する関数です。
基本構文
(position-if pred lst . opt)
今回実装するインターフェース
| 引数 | 内容 |
|---|---|
| pred | 述語関数(1引数の関数。各要素に対して真偽値を返す。) |
| lst | 対象のリスト |
| . opt | オプション - ":start"のみ(デフォルト0) |
では、今回習得した実装技術を以下に説明します。| ゚Д゚| ノ イッテミヨー
名前付きletループ
下に名前つきlet(named let)の構文例を示します。
再帰呼び出しとループ処理の合わせ技のような感じで使います。
太古の昔にBASICで使っていたgotoラベルに近いものを感じます。
(let loop ((x xの初期値セット) (y yの初期値セット))
;; ↓ 上で定義したx, y を使った本体の処理
(if (終了条件が真か?)
結果を返す ; trueの場合、結果を返す
(loop 新しいx 新しいy) ; falseの場合、loopに戻る
)
)
コマンドパラメータのパース処理なんかでよく目にしそうな構文ですね。
1からnまでの合計を求める関数で上の構文例を試してみましょう。
(define (test_nl n)
(let sum-to-n ((i 1) (acc 0)) ; 名前付きlet (sum-to-n)
(if (> i n)
acc ; trueの場合、合計値を返す。
(sum-to-n (+ i 1) (+ acc i)) ; falseの場合、sum-to-nを繰り返す
)
)
)
実行例
(test_nl 4) ; => 10
応用例として述語関数(各種のtrue/false条件チェック関数)と検索対象の可変長引数を指定するだけのposition-ifを下に示します。
(define (position-if pred lst_)
(let search ((lst lst_) (i 0)) ; 名前付きlet (search)
(cond ((null? lst) #f) ; リストが空の場合、false
((pred (car lst)) i) ; 述語関数の評価結果がtrueの場合
(else (search (cdr lst) (+ i 1))) ; その他の場合、searchを繰り返す
))
)
実行例
(position-if even? '(1 3 4 6)) ; => 2
(position-if char-alphabetic? '(#\1 #\@ #\A #\b)) ; => 2
(position-if (lambda (x) (> x 10)) '(2 5 8 12 3)) ; => 3
可変長引数
関数に任意の数の引数をリストとして渡す仕組みです。
業務アプリ開発では他のメジャープログラミング言語でおなじみ機能ですね。
下に使い方例を示します。渡された引数を表示するだけです。
- paramは固定引数です。
- '.'に続くparamsが可変長引数になります。全部まとめてリストとして扱われています。
(define (scanparams param . params)
;; 第1引数を表示する
(display "param : ") (display param) (display ", ")
;; 第2引数以後の可変長引数を表示する
(if (null? params)
(begin
(display "params :指定なし.") (newline))
(begin
(display "params : ") (display params) (newline)))
)
実行例
(scanparams 1 2 3 4) ; => param : 1, params : (2 3 4)
(scanparams 'x 'y 'z) ; => param : x, params : (y z)
(scanparams 'only) ; => param : only, params :指定なし.
可変長引数を渡す関数の再帰呼び出し時の注意
可変長引数はリストとして関数に渡されますが再帰呼び出しをするとさらにリスト化が繰り返されます。
可変長引数はリストとして渡されるわけなので当然ですが、はまりました。
cadrで次の要素を参照しようとしても空リストしか取れなくて。。。
( ´Д`)=3
下は再帰呼び出しを2回繰り返したときの可変長引数の渡され具合を表示するテストコードです。
(define (test-vargs n . vargs)
(begin
(display "vargs: ") (display vargs) (newline)
(if (< n 2) (test-vargs (+ n 1) vargs))
)
)
(test-vargs 0 'foo 1 'bar 2) ; 実行例
上の関数を実行した結果
vargs: (foo 1 bar 2)
vargs: ((foo 1 bar 2))
vargs: (((foo 1 bar 2)))
キーワード風引数
最後にキーワード風引数の実装サンプルを見てみましょう。
実装するキーワードは次のようなペア(項目名 値)で表現しています。
(:foo 1 :bar 2)
キーワード風引数を複数指定でるようにする場合は可変長引数を使えばよいですね。
下に指定のキーワードとペアになっている値を取得する場合のサンプルを示します。
;;key_に該当するオプションの値をopts_から取得する。
(define (test-option key_ . opts_)
(define (get-option key opts default)
(cond
((null? opts) default)
((and (pair? opts) (eq? (car opts) key))
(if (and (pair? (cdr opts)))
(cadr opts)
default)
)
(else (get-option key (cdr opts) default)))
)
(print (get-option key_ opts_ "キーの値が指定されいません。"))
)
実行例
(test-option) ; => キーの値が指定されいません。
(test-option ':foo ':foo 1) ; => 1
(test-option ':bar ':foo 1 ':bar 2) ; => 2
(test-option ':hoge ':foo 1 ':bar 2) ; => 「キーの値が指定されいません。」
上のコードを使ってposition-ifで開始位置を示すオプション(:start)に対応させることができます。
(define (position-if pred lst_ . opt_)
;; オプション引数から :start キーを探す
(define (get-start key opts default)
(cond ((null? opts) 0)
((and (pair? opts) (eq? (car opts) key))
(if (and (pair? (cdr opts)) (number? (cadr opts)))
(cadr opts)
default)
)
;(else (get-start (cdr opts))) ;バグ修正 keyとdefaultが渡されいない
(else (get-option key (cdr opts) default)))
)
)
(let ((start (get-start ':start opt_ 0)))
(let search ((lst lst_) (index 0))
(cond ((null? lst) #f)
((< index start) (search (cdr lst) (+ index 1)))
((pred (car lst)) index)
(else (search (cdr lst) (+ index 1)))
)
)
)
)
動作確認
(position-if even? '(1 3 4 6 8))
(position-if even? '(1 3 4 6 8) ':start 3) ; => 3
(position-if char-alphabetic? '(#\1 #\@ #\A #\b) ':start 1) ; => 2
おわりに
文字列サポート、position関数の応用まで広げようと思いましたが..( ゚д゚)アキタヨ…
JSライブラリにLISP機能をモデルにしたunderscore.jsをご存知の方は多いと思いますが、Lips使えばフルスペックになるということかも??。Σ(゚д゚`)ヌオッ!!
余談
私事ではありますが、先日(9月17・18日)、地元のシルバー人材センター主催の清掃作業員講習会に参加しました。
建材の材質の見分け方、洗剤の選び方、道具の選び方、モップやほうきの使い方等結構覚えることがありました。
モップのかけ方も講師のデモを見ると簡単そうですが、素人(and Silver man)には難しかったです。
なめてました。m(_ _)mシツレイシマシタ
現場が冷房のきいた屋内であれば考えてみようと思っています。