プログラミングを頑張る日記

プログラミングを勉強して、ハッカーになろう

Common Lispを頑張る(18)

木曜日ですね。明日が終われば仕事も一段落します。
「実践Common Lisp」やっていきます。
昨日に引き続き、変数のことを学びます。

色々Common Lispのサイトを巡っていたら多少は
ダイナミック変数についてわかってきました。
特に参考にしたのは下記のサイトです。
Lispのダイナミック・スコープとスペシャル変数 · wshito's diary

ローカル変数によって上書きされているのはそのとおりで、
スコープ内の他の関数の中でダイナミック変数を見ようとした時にも
ローカル変数によって覆い隠される、そしてそれがダイナミック変数の
特殊なところだと理解しました。

昨日書いた関数を確認します。

CL-USER> (defun foo ()
	   (format t "A : ~a~%" *hey*)
	   (setf *hey* (concatenate 'string *hey* "!"))
	   (format t "B : ~a~%" *hey*))
WARNING: redefining COMMON-LISP-USER::FOO in DEFUN
FOO
CL-USER> (defun bar ()
	   (foo)
	   (let ((*hey* "how are you?")) (foo))
	   (foo))
BAR
CL-USER>

先程のサイトの二番煎じですが、上の関数のスペシャルじゃない
バージョンを作ってやってみます。

CL-USER> (setf hey "nice to meet you")
; in: SETF HEY
;     (SETF HEY "nice to meet you")
; ==>
;   (SETQ HEY "nice to meet you")
; 
; caught WARNING:
;   undefined variable: HEY
; 
; compilation unit finished
;   Undefined variable:
;     HEY
;   caught 1 WARNING condition
"nice to meet you"
CL-USER> hey
"nice to meet you"
CL-USER> (defun foo ()
	   (format t "A : ~a~%" hey)
	   (setf hey (concatenate 'string hey "!"))
	   (format t "B : ~a~%" hey))
; in: DEFUN FOO
;     (FORMAT T "A : ~a~%" HEY)
; 
; caught WARNING:
;   undefined variable: HEY
; 
; compilation unit finished
;   Undefined variable:
;     HEY
;   caught 1 WARNING condition
WARNING: redefining COMMON-LISP-USER::FOO in DEFUN
FOO
CL-USER> (defun bar ()
	   (foo)
	   (let ((hey "how are you?")) (foo))
	   (foo))
; in: DEFUN BAR
;     (LET ((HEY "how are you?"))
;       (FOO))
; 
; caught STYLE-WARNING:
;   The variable HEY is defined but never used.
; 
; compilation unit finished
;   caught 1 STYLE-WARNING condition
WARNING: redefining COMMON-LISP-USER::BAR in DEFUN
BAR
CL-USER>

警告だらけですが作れました。
実行します。

CL-USER> (bar)
A : nice to meet you
B : nice to meet you!
A : nice to meet you!
B : nice to meet you!!
A : nice to meet you!!
B : nice to meet you!!!
NIL
CL-USER>

LETスコープ内で読んだfooで参照されているheyは
グローバルな変数の方ですね。
なるほど…。ダイナミック変数、多少わかった気がします。
というかそうかあ。スコープの中で関数を呼んだら
その関数の中の処理が呼び元のスコープの中で行われる、という風に
考えていた昨日の自分が阿呆でした。反省。

参考書の続きに行きます。
そんなダイナミックな束縛を作る秘密は、そのダイナミック変数名は
スペシャル”だと宣言されているからだそうです。
DEFPARAMETERやDEFVARで宣言した変数の名前は、
自動的に全てグルーバルにスペシャルであると宣言されます。
それ以降、束縛フォームの中で同じ名前を使えば、そこがどこでも
ダイナミックな束縛が作られるということです。
そしてそれがグローバルな変数に耳あてをつける理由だとか。
絶対にごっちゃにしないための作戦だということですね。

ローカルにスペシャル宣言もできるそうですが、使わないので気にしません。
ダイナミック変数のおかげでグローバルな変数を使いやすいですが、
下流のコードの振る舞いを変更可能なこと
下流のコードがスタック上の束縛に新しい値を代入できること
これらはダイナミック変数の利点であり欠点でもあるので、
どちらかが必要な場合にだけ使おう、ということです。

ちょっと勉強ばっかで飽きてきたので、
今までに覚えたことを使って何かプログラムを作ります。
う〜ん。何かあったり便利なものあるかなあ。
あ、ありました。このブログ、いつも記事のタイトルが
「〜を頑張る(n)」になっているのですが、このnの部分、
いつも第何回か忘れちゃうんですよね。
これを覚えていてくれるプログラムを作ってみます。

LispC++の二種類あるので、リストかな、
(勉強する言語 記事数)な要素を持つリストを作ります。

CL-USER> (defparameter *programing-lang* 'lisp)	 
*PROGRAMING-LANG*
CL-USER>

やっぱりやめました。記事数はレキシカル変数で管理したくなりました。
そして今回冒頭で参考として出したサイトにダイナミック変数は
初期値として使う、という話があったのを思い出したので、
勉強中の言語の初期値はやっぱりLispかな、と思いこうなりました。

う〜ん、どんな処理が必要かうまくイメージできません。
とりあえず書いてみます。

CL-USER>  (defun check-blog-title (&key (last 0) (lang *programing-lang*))
	   (let ((n 0))
	    (unless (= last 0) (let ((n last))))
	    (when (= last 0)
	      #'(lambda () (setf n (1+ n))
		  (let ((*programing-lang* lang))
		    (print-blog-title))))))
; in: DEFUN CHECK-BLOG-TITLE
;     (LET ((N LAST)))
; 
; caught STYLE-WARNING:
;   The variable N is defined but never used.
; in: DEFUN CHECK-BLOG-TITLE
;     (PRINT-BLOG-TITLE)
; 
; caught STYLE-WARNING:
;   undefined function: PRINT-BLOG-TITLE
; 
; compilation unit finished
;   Undefined function:
;     PRINT-BLOG-TITLE
;   caught 2 STYLE-WARNING conditions
WARNING: redefining COMMON-LISP-USER::CHECK-BLOG-TITLE in DEFUN
CHECK-BLOG-TITLE
CL-USER> (check-blog-title)
#<CLOSURE (LAMBDA () :IN CHECK-BLOG-TITLE) {10041ACA1B}>
CL-USER>

check-blog-titleは単純に今の記事数を1増やしてprint-blog-titleに
つなげる役目のつもりです。間違えて呼びすぎたりした時用に、
キーワード引数としてlastを持っており、lastに引数を設定すると
次に普通に使った時、前に設定したlastが最後の記事の数想定になるはずです。
langキーワードは現在お飾りですね。…LispC++で当然記事数が違うので
それに合わせてホニャララ、というのを考えてパラメータとしましたが、
今の所どちらかに合わせるしかない感じです。
次はprint-blog-titleを作ります。

CL-USER> (defun print-blog-title (num)
	   (format t "~a~%"
		   (concatenate 'string *programing-lang* "を頑張る("
				num ")")))
PRINT-BLOG-TITLE
CL-USER> (defun check-blog-title (&key (last 0) (lang *programing-lang*))
	   (let ((n 0))
	    (unless (= last 0) (let ((n last))))
	    (when (= last 0)
	      #'(lambda () (setf n (1+ n))
		  (let ((*programing-lang* lang))
		    (print-blog-title n))))))
; in: DEFUN CHECK-BLOG-TITLE
;     (LET ((N LAST)))
; 
; caught STYLE-WARNING:
;   The variable N is defined but never used.
; 
; compilation unit finished
;   caught 1 STYLE-WARNING condition
WARNING: redefining COMMON-LISP-USER::CHECK-BLOG-TITLE in DEFUN
CHECK-BLOG-TITLE
CL-USER>

ただブログのタイトルを作って表示するだけです。
パラメータで記事数を受け取りたいので、前の関数もちょっと修正。
さて、関数オブジェクトを起動する関数を書いて…
関数オブジェクトじゃなくて関数を定義してることに今やっと気づきました。
修正します…。

CL-USER> (defparameter *check-blog-title* 
	   (let ((n 0))
	     #'(lambda (&key (last 0) (lang *programing-lang*))
		 ((unless (= last 0) (let ((n last))))
		  (when (= last 0)
		    (setf n (1+ n))
		    (let ((*programing-lang* lang))
		      (print-blog-title n)))))))
; in: DEFPARAMETER *CHECK-BLOG-TITLE*
;     ((UNLESS (= LAST 0) (LET ((N LAST))))
;      (WHEN (= LAST 0)
;        (SETF N (1+ N))
;        (LET ((*PROGRAMING-LANG* LANG))
;          (PRINT-BLOG-TITLE N))))
; 
; caught ERROR:
;   illegal function call

;     #'(LAMBDA (&KEY (LAST 0) (LANG *PROGRAMING-LANG*))
;         ((UNLESS (= LAST 0) (LET (#)))
;          (WHEN (= LAST 0)
;            (SETF N #)
;            (LET (#)
;              (PRINT-BLOG-TITLE N)))))
; 
; caught STYLE-WARNING:
;   The variable LAST is defined but never used.
; 
; caught STYLE-WARNING:
;   The variable LANG is defined but never used.

;     (LET ((N 0))
;       #'(LAMBDA (&KEY (LAST 0) (LANG *PROGRAMING-LANG*))
;           ((UNLESS (= LAST 0) (LET #))
;            (WHEN (= LAST 0)
;              (SETF #)
;              (LET #
;                #)))))
; 
; caught STYLE-WARNING:
;   The variable N is defined but never used.
; 
; compilation unit finished
;   caught 1 ERROR condition
;   caught 3 STYLE-WARNING conditions
*CHECK-BLOG-TITLE*
CL-USER>

またすごい怒られました。
警告は置いといてエラーを見ると、関数を不正に呼ぼうとしてる?
エラー文でググったらドンピシャなのが出てきました。
https://stackoverflow.com/questions/16002497/lisp-illegal-function-call

てことは、"((unless"がダメですね。prognという手も紹介されていますが、
おとなしくcondにしようと思います。

CL-USER> (defparameter *check-blog-title* 
	   (let ((n 0))
	     #'(lambda (&key (last 0) (lang *programing-lang*))
		 (cond ((not (= last 0)) (let ((n last))))
		       ((= last 0)
			(setf n (1+ n))
			(let ((*programing-lang* lang))
			  (print-blog-title n)))))))
; in: DEFPARAMETER *CHECK-BLOG-TITLE*
;     (LET ((N LAST)))
; 
; caught STYLE-WARNING:
;   The variable N is defined but never used.
; 
; compilation unit finished
;   caught 1 STYLE-WARNING condition
*CHECK-BLOG-TITLE*
CL-USER>

うん、だいぶ静かになりました。
さて、やっと関数を呼ぶ関数です。

CL-USER> (defun get-title ()
	   (funcall *check-blog-title*))
GET-TITLE
CL-USER>

こっからキーワード引数渡すのもしかして面倒…?
……まあいいや、実行します!

CL-USER> (get-title)
; Evaluation aborted on #<TYPE-ERROR expected-type: SEQUENCE datum: LISP>.
CL-USER> (defparameter *programing-lang* "Common Lisp")
*PROGRAMING-LANG*
CL-USER> (get-title)
; Evaluation aborted on #<TYPE-ERROR expected-type: SEQUENCE datum: 2>.
CL-USER> (defun print-blog-title (num)
	   (format t "~a~d~a~%"
		   (concatenate 'string *programing-lang* "を頑張る(") num ")"))
WARNING: redefining COMMON-LISP-USER::PRINT-BLOG-TITLE in DEFUN
PRINT-BLOG-TITLE
CL-USER> (get-title)
Common Lispを頑張る(3)
NIL
CL-USER>

ちょこちょこエラーが出ましたが、concatenateが変なの渡すなと
怒っているだけだったのでささっと解決です。
とりあえず思ったとおりには動いてくれそうです。
記事数の修正ができないのを除けばですが。
本当はそこらへんもしっかりやりたいのですが、例によってもう寝たいです。

設定だけ手入力でやって、後は記事タイトル出力して投稿して寝ます。
え〜と、昨日の記事は(17)ですね。

CL-USER> (funcall *check-blog-title* :last 17)
NIL
CL-USER> (get-title)
Common Lispを頑張る(4)
NIL
CL-USER>

げ、マジか。こればっかは寝る前に修正したいところ。

CL-USER> (defparameter *set-blog-number* 
	   (let ((n 0))
	     #'(lambda (&key (last 0) (lang *programing-lang*))
		 (cond ((not (= last 0)) (setf n last))
		       ((= last 0)
			(setf n (1+ n))
			(let ((*programing-lang* lang))
			  (print-blog-title n)))))))
*SET-BLOG-NUMBER*
CL-USER> 

見直したらごくごく当然でした。
LETフォームで完全に閉じてた箇所をSETFに変更しました。
それと、checkと言いながら値の更新しているのは良くない気がしたので
関数オブジェクトの名前を変更しました。
get-titleの中もそれに合わせて変更しています。

さあ、修正後実行してみます。

CL-USER> (funcall *set-blog-number* :last 17)
17
CL-USER> (get-title)
Common Lispを頑張る(18)
NIL
CL-USER>

やっと動いた…!
誇れるようなプログラムではありませんが、誇らしい気持ちでいっぱいです。
やっとブログタイトルのナンバリングに迷わなくてすみます!

タイトルがわかったので記事投稿して寝ます!おやすみなさい!