プログラミングHaskell 第5章 リスト内包表記

内包とはなんだろう。

リスト内包表記とあるが、内包とはなんだろう。

外延と内包 [編集]
ある概念を類と見たとき、その類に含まれる種の全てを外延(Extension)、その種に共通な性質を内包(Intension)という。種を全て示すことを外延的説明、共通な性質を示すことを内包的説明という。
例) 集合 A は {1, 3, 5, 7, 9} からなる。(外延的説明)
例) 集合 A は 10 以下の奇数である。(内包的説明)
これらを定義にも使える。
例) A とは、{1, 3, 5, 7, 9} を要素とする集合である。(外延的定義)
例) A は、10 以下の奇数からなる集合である。(内包的定義)

http://ja.wikipedia.org/wiki/%E5%AE%9A%E7%BE%A9#.E5.A4.96.E5.BB.B6.E3.81.A8.E5.86.85.E5.8C.85

なるほどね。コードでいえば、

[x^2|x<-[1..5]] -- こっちがリスト内包表記
[1,4,9,16,25]   -- こっちが外延的定義

ということなわけだ。

生成器

でリスト内包表記では、x<-[1..5]という部分を生成器という。
生成器はカンマで区切ることで複数列挙できる。

Prelude> [(x,y)|x<-[1..3],y<-[1,2]]
[(1,1),(1,2),(2,1),(2,2),(3,1),(3,2)]

上の例ではxの要素数が3でyの要素数が2だから、3×2=6個の組ができるわけね。
じゃあ、生成器が使う変数を使わなかったらどうなるのか?

Prelude> [x|x<-[1..3],y<-[4,5]]
[1,1,2,2,3,3]

yの値は使われないが、生成されるリストの要素数は6つで変わらない。

プログラミングHaskell 第4章 関数定義

条件式

Haskellの場合は、条件式では必ずelse部を持たないといけないらしい。

myAbs :: Int -> Int
myAbs n = if n >= 0 then n 

という風にelse部を省略したmyAbsをtest.hsに定義して実行してみる。

Prelude> :load test.hs
test.hs:3:1: parse error (possibly incorrect indentation)
Failed, modules loaded: none.

と怒られる。まあ、ifの条件がFalseになったときにmyAbsの値が定まらないので当たり前か。
ところで、haskellではifは文じゃなく、式なんだね。

ガード付きの等式

myfunc :: Int -> String
myfunc n | n > 0 = "positive"
myfunc n | n < 0 = "negative"

をtest2.hsに入力して実行してみる。
上のmyfuncにはn == 0のケースが含まれていない。

Prelude> :load test2.hs
[1 of 1] Compiling Main             ( test2.hs, interpreted )
Ok, modules loaded: Main.
*Main> myfunc 0
"*** Exception: test2.hs:(1,1)-(2,29): Non-exhaustive patterns in function myfunc

コンパイルのときには怒られずに、実行時にエラーになる。ifのときはelseを書かないとダメだったが、いつもチェックが厳しいわけでもないらしい。
それと、

*Main> myfunc -1
<interactive>:1:9:
    No instance for (Num (a0 -> [Char]))
      arising from the literal `1'
    Possible fix: add an instance declaration for (Num (a0 -> [Char]))
    In the second argument of `(-)', namely `1'
    In the expression: myfunc - 1
    In an equation for `it': it = myfunc - 1
*Main> myfunc (-1)
"negative"

というように-1を引数として与える場合は、(-1)と括弧でくくらないとダメみたい。
パターンマッチも同じだった。

セクション

セクションの定義がよくわからなかったので、Webで調べてみたら、(中置)演算子の部分適用のことをセクションというらしい。納得。また、演算子も関数で、引数を2つ取る関数は関数名をバッククォートで囲むと演算子になる。

プログラミングHaskell 第3章 型とクラス

型を知る

:type

頭に':'が付いているんだけど、":名前"という形式にはどんな意味があるのだろうか?とりあえずスルー。本にはHugsではと書かれているがghciでも動いた。

多相型

型変数とはなんだろうか?

length::[a]->Int

wikipediaにあった!

型変数を使い、型を抽象化することもできる。これは C++ のテンプレートや Java のジェネリクスが型を抽象化するのに相当する。例えば、a 型の値をとり、それをそのまま返す恒等関数 id を型の指定とともに定義すると以下のようになる。ここで任意の型を示す型名 a が定義に使われているが、このように先頭が小文字で始まっている型名は具体的な型名ではなく型変数である。この関数はあらゆる型の値を引数にとることができる。

http://ja.wikipedia.org/wiki/Haskell/

多重定義型

クラス制約ってなんだろう。

(+) :: Num a => a -> a -> a
negate::Num a => a -> a

ああ、上の例でいうところの"Num a =>"というところがクラス制約になのか。で多重定義型というのはこのクラス制約が一つ以上持っている型らしい。

感想

最初読んだときに内容が頭の中にすっと入らなかった。

Int -> Int

って書かれると、それは型ではなくて、Int型を取りInt型を返す関数と思ってしまう。だから、そのあと出てくる、「型に(型変数|クラス制約)を含める」という内容を見てもピンとこなかった。わかってしまえば何のことはないんだけど。

SBCLにお引っ越し

Allegro CL Express Editionは好きなのだけど。

いままでAllegro CL Express EditionをLispboxから使っていたのだけど、やめることにした。Allegro CLはFranz社が作っている商用のLisp開発環境。Windows版だとIDEとかついている。機能も沢山あるし、初心者の自分からしたら、Express Editionの限界(Heapサイズ)にぶつかって苦しむこともない。

理由は、

  • ライセンス更新が面倒くさい。

-asdf-installがうまくいかない。あくまで自分の場合だけ。asdf-installを使えないわけではない。
この2点。ライセンス更新は、30日経過すると製品が使えなくなるので、新しいライセンスをもらえばいい。ライセンス更新は何度でもできるので実質、ずっと使い続けられる。でもこのライセンス更新。当然ネットがないとできない。
ネットに繋がっていないとき、ふっと使いたくなることがあって、何回かライセンス切れに直面して涙した。

かわりの処理系は

Lispboxを見ると、WindowsならCLISPがある。でもCLISPでasdf-installの設定に苦しんだというBlogを見て、やめることにした。以前、Lispboxの設定をいじった時に、slimeの設定をいじったので、Lispboxに頼らなくてもいけるような気がするので、windowsでasdf-installが動く実績のある処理系であるSBCL(Steel Bank Common Lisp) を持ってきて、環境を作ることにした。

SBCL

windowsプラットフォーム用のインストーラーがあるので、ダウロードしてインストールするだけ。簡単。

Emacs(Meadow)

特に理由がないが、Windows用のEmacsとしてMeadowを使うことにした。MeadowのダウンロードサイトでMeadow 3.01 開発版のSetup.exeをダウンロードして実行するだけ。こちらも簡単。

SLIME

slimeのダウンロードサイトのページにあるcvs snapshotで最新のSLIMEをダウンロード。tgzファイルを解凍して、slime-2009-11-22というフォルダをslimeにリネームして、C:\meadow\lispの下に置く。

.emacsは、
http://cadr.g.hatena.ne.jp/bbs/3
を参考にして、

;; slime
(setq inferior-lisp-prograpm "C:/Program Files/Steel Bank Common Lisp/1.0.32/sbcl.exe")
(add-to-list 'load-path "C:/Meadow/lisp/slime")
(require 'slime)
(slime-setup '(slime-fancy slime-banner))

(setq slime-lisp-implementations
      `((sbcl ("sbcl") :coding-system utf-8-unix)
	(cmucl ("lisp"))))

(add-hook 'lisp-mode-hook
	  (lambda ()
	    (require 'slime)
	    (local-set-key "\t" 'slime-indent-and-complete-symbol)))

(add-hook 'slime-mode-hook
	  (lambda ()
	    (require 'cltl2)
	    (local-set-key "\C-c\C-dc" 'cltl2-lookup)))

;; hyperspec
(setq common-lisp-hyperspec-root 
      (concat "file://" 
	      (expand-file-name "~/Documents/commonLisp/HyperSpec/")))
(setq common-lisp-hyperspec-symbol-table
      (expand-file-name "~/Documents/commonLisp/HyperSpec/Data/Map_sym.txt"))
(setq slime-truncate-lines nil)
(setq slime-enable-evaluate-in-emacs t)

MSYS

asdf-installでtarを呼び出すので、wikipedia:MSYSをインストールする。
http://www.mingw.org/wiki/msysの"Installing MSYS"によると、
MSYSはMinGWを必要とするので、MinGWを先にインストールする。
次にMSYSをインストール。デフォルトでは、
C:\msys\1.0
以下にインストールされる。

.sbclrcの作成

http://d.hatena.ne.jp/y2q_actionman/20091025/p1
の内容を参考にして(ほとんどそのまま)以下の内容の.sbclrcをホームディレクトリに作成

(require :asdf)
(in-package :asdf)

(defvar *win-central-registry*
  `((,(user-homedir-pathname) ".sbcl/site/")
    (,(sb-ext:posix-getenv "SBCL_HOME") "site/")))

(defun win-sysdef-search (system)
  (dolist (dir *win-central-registry*)
    (let ((files (directory
                  (merge-pathnames
                   (make-pathname :directory '(:relative :wild)
                                  :name (coerce-name system)
                                  :type "asd"
                                  :case :local)
                   (merge-pathnames (cadr dir) (truename (car dir)))))))
      (when files
        (return (car (sort files #'string> :key #'namestring)))))))

(pushnew `win-sysdef-search asdf:*system-definition-search-functions*)

 
(require :asdf-install)
(in-package :asdf-install)
(setq *tar-program* "C:/Msys/1.0/bin/tar.exe")
 
(labels ((pathstr (win-path)
	   (cl:concatenate 'string "/c" 
			   (cl:substitute #\/ #\\ 
					  (cl:subseq (namestring win-path) 2))))
         (tar (args)
           (with-output-to-string (o)
             (let ((process (sb-ext:run-program *tar-program*
                                                args
                                                :search t
                                                :wait nil
                                                :output :stream)))
               (prog1 (loop for l = (read-line (process-output process) nil nil)
			 while l
			 do (write-line l o))
                 (process-wait process)
                 (process-close process))))))
  
  (defun get-tar-directory (packagename)
    (let* ((tar (tar (list "-tzf" (pathstr packagename))))
           (first-line (subseq tar 0 (position #\newline tar))))
      (if (find #\/ first-line)
          (subseq first-line 0 (position #\/ first-line))
          first-line)))
  
  (defun untar-package (source packagename)
    (tar (list "-C" (pathstr source)
               "-xzvf" (pathstr packagename)))))


(in-package :cl-user)

asdf-installがダウンロードしたモジュールをインストールするディレクトリを作成する。

cd c:\Users\patterson
mkdir .sbcl\site
mkdir .sbcl\systems

asdf-installでcl-ppcreをインストールする

最後にREPLで、

CL-USER>(asdf-install:install :cl-ppcre)

GPGのキーがありませんと怒られるので、スキップするとインストールできた。

SLIMEのカスタマイズ

SLIMEはとっても便利なのだけれど、ちょっとだけ気に入らないところがあった。

  • Hyperspecを参照できるのは便利なのだけど、ネットにつながらないと見れない。
  • RPELで入力しているときには、TABを押すとシンボルの候補が出てきて入力が楽なのに、lispのソースを編集しているときには、なぜか有効にならない。
  • コントロールキーの位置がよくないので、キーが押しにくい。(SLIMEとは直接関係ないけど)

カスタマイズしまくれるemacsだからきっと簡単に挙動を変更できるのだろうけれど、面倒に思ってずっと放置していた。でも最近我慢できなくなってきたので、ここで重い腰を上げることにした。

lispboxに甘やかされた私

自分はLispの開発環境としてlispbox(acl)を使っている。このlispbox、All in oneでなーにもしなくても快適なlisp環境が使える素晴らしいものなのだけど、それだけにどこをいじればよいかいまいちわかっていない。自分はいままでemacsをそれほど(いや全然といった方がよいか)使っていなかったので、「まあ、.emacsに何か書けばよいだろう」程度に思っていた。

ローカルのhyperspecを参照する

ダウンロードしたhyperspecを参照するための設定はすぐに見つかった
手順は、

  1. http://www.lispworks.com/documentation/common-lisp.htmlにあるhyperspecのダウンロードのリンクをクリックしてhyperspec(HyperSpec-7-0.tar.gz)をダウンロードする。
  2. ダウンロードしたhyperspecをお好みの場所に解凍する。
  3. ~/.emacsに以下の内容を追加。下の例ではHyperSpec-7-0.tar.gzを~/doc以下に解凍した場合。
;; hyperspec
(setq common-lisp-hyperspec-root 
      (concat "file://" 
	      (expand-file-name "~/doc/HyperSpec/")))
(setq common-lisp-hyperspec-symbol-table
      (expand-file-name "~/doc/HyperSpec/Data/Map_sym.txt"))

割と簡単なので安心。ところが、内容を追加した後、emacsを起動したら、まったく設定が有効になっていなかった。

lispbox

なんで設定が有効にならないだろうと思って、調べていたら、lispboxではemacsを起動するときに.emacsを読まないようになっているからだった。.emacsをいじり倒している人がlispboxをインストールしてもちゃんと動くようにという配慮からなのだろうか。自分の場合他の人がPCを使うこともないので、C:\Program Files\LispBox-0.7\lispbox.batを直接変更して.emacsを読むようにしてしまった。具体的にいうとrunemacs.exeの引数についている--no-init-fileを削除した。

echo off
set SHELL=C:/Program Files/LispBox-0.7/emacs-22.1/bin/cmdproxy.exe
"C:\Program Files\LispBox-0.7\emacs-22.1\bin\runemacs.exe" --no-site-file -l "C:\Program Files\LispBox-0.7\lispbox.el"

これで、hyperspecはめでたくローカルのファイルを参照するようになった。

lispソースファイル編集時のキーバインドの変更

次は、lispソースを編集中にTABキーを押したら、シンボル補完をするようにする。
ソースを編集しているときにはM-TABでシンボル補完が動くのだけれど、いちいちESCを押すのがとても面倒くさい。emacs使いの人はESCを押すのに慣れているかもしれないが、自分は無理。REPLでTABだけで補完をやってくれるのだから、こっちもそうしてくれればいいのにと思う。Googleで調べてみると(local-set-key)とか(global-set-key)とかやるとキーバインドを変更できるようなので、両方試してみるがまったく変わらない。

現在のキーバインドを調べる

では、現状はどのキーバインドを調べてみる。M-x describe-bindingsでキーバインドの一覧がわかる。これでみるとTABはlisp-indent-lineというのにアサインされている。これだとインデントがきれいに設定されるものの、補完機能は動きそうにない。

メジャーモードとフック

さっぱりわからないなーと思いながら、色々探しているとemacsにはメジャーモードとマイナーモードというモードがあるらしいことがわかった。メジャーモードは編集モード(ここでの場合lispソースの編集モード)に対して一つしか選べないらしい。lispコードの編集ではlisp-modeというメジャーモードになるらしく、このときTABキーがlisp-indent-lineにバインドされてしまうようだ。
一方、マイナーモードというのは一つの編集モードに複数設定できる。Slimeはマイナーモードらしい。では、lispモードのときにどうやってTABキーを変更するかだけれど、lisp-modeがロードされると呼び出されるフック関数でキーバインドを変えるとよいらしい。

TABキーで呼び出す機能の名前

ではTABを押したときに何を呼び出したらいいのだろうか、自分が期待するのは、TABを押したときには、シンボルの補完なのだけど、補完しかやってくれないとなると、インデントが設定されなくなってしまう。うーん、それはそれで困るなー。REPLでは何がアサインされているのだろう?むむっ!slime-indent-and-complete-symbolなんてのがある。安易な感じだけどまさに欲しい機能なので、以下のように.emacsに追加してみる。

(add-hook 'lisp-mode-hook
	  (lambda ()
	    (require 'slime)
	    (local-set-key "\t" 'slime-indent-and-complete-symbol)))

おぉ、思ったように動いた。
設定方法が正しいのか、正直あやしいけど、まあよしとしよう。

実践Common lispを読み始めた-第20章 特殊オペレータ

フォームの評価を制御する特殊オペレータ

  • QUOTE 評価をしないでS式をデータとして扱えるようにする。
  • IF 条件実行の構文要素の基礎になるbool選択の操作。
  • PROGN 複数の式を並べられるようにする。

レキシカル環境を操作する

  • letとlet*は変数に対してレキシカルな束縛を確立できる。つまり特定のスコープ内で有効な変数が定義できる。
  • setqはletとlet*で束縛された変数の設定に使えるので、レキシカル環境にアクセスできる特殊オペレータだといえる。うーん、よくわからない。さまよっていたら、わかりやすい解説を発見LISPをみんなで勉強しよう! : Re: setqはset quote。なるほど、特定のスコープの中でのみ有効な変数を、スコープを変えないようにして中身の値だけ変えられるのがsetqで、setはスコープも変えてしまうわけだ。
CL-USER> (let ((foo 42))
	   (setq foo (+ 1 42))
	   foo)
43
CL-USER> foo
; Evaluation aborted
CL-USER>  (let ((foo 42))
	   (set 'foo (+ 1 42))
	   foo)
42
CL-USER> foo
43

多値

多値を返すのはリストを返すの異なり、多値を捕えるための操作を行わない限り、1つめの値以外は捨てられる。

CL-USER> (defparameter *h* (make-hash-table))
*H*
CL-USER> (setf (gethash 'a *h*) 10)
10
CL-USER> (setf (gethash 'b *h*) 1000)
1000
CL-USER> (+ (gethash 'a *h*) (gethash 'b *h*))
1010
CL-USER> (+ (gethash 'a *h*) (gethash 'c *h*)) ;;*h*にはキーcがない
; Evaluation aborted
CL-USER> 

valuesは引数を多値として返す関数で、values-listはリストを一つとり中身の要素を多値として返す。

CL-USER> (values '(1 2 3))
(1 2 3)
CL-USER> (values 1 2 3)
1
2
3
CL-USER> (values-list '(1 2 3))
1
2
3
CL-USER> (values-list '(1 2 (3 4)))
1
2
(3 4)

逆に多値を変数に格納したり、リストに格納する場合には、multiple-value-bindやmultiple-value-listを使うう。
setfの引数としてvaluesの戻り値を設定すると、複数の変数をまとめて設定できる。

EVAL-WHEN

その他の特殊オペレータ

CL-USER> (defun bar ()
	   (load-time-value (get-universal-time)))
BAR
CL-USER> (bar)
3450841236
CL-USER> (bar)
3450841238  <-値が変わっている
CL-USER> (bar)
3450841240  <-値が変わっている
CL-USER> (compile 'bar)
BAR
NIL
NIL
CL-USER> (bar)
3450841247
CL-USER> (bar)
3450841247  <-値が変わらない
CL-USER> (bar)
3450841247  <-値が変わらない
CL-USER> 

Windows7 RC

巷では、Windows Vistaが重いので、XPの次はVistaを飛ばしてWindows7に移るという話をよく聞く。相当軽いのだろう。RCが最近出たのでインストールしてみた。
あくまで体感上の印象という前提で、誤解を恐れずに言うと、Vistaとくらべてそんなに軽いのかなというのが正直なところ。さらに誤解を恐れずに言うと自分のPCのOSをXPからVistaに変えた時もVistaの重さが気にならなかった。
いくつかの要因がある。

  • 確かに起動時間はVistaがXPや7よりもかかっている。でも自分は普段PCを使わないときはスリープ状態にしているので、起動時間の長さはあまり気にならない。
  • 自分のPCはRAMが4GBytes入っている。使っているOSは32bitなので、実際には3GBytes少々しか使われないが、メモリに関してはあまり心配しなくても大丈夫。多少サービスがたくさん立ち上がっていても物理メモリが足りなくなることはないし、よく使うアプリケーションはメモリ上にキャッシュされる。NetBookのように大半が1GBytesが標準だとVistaにはしんどいのだと思う。
  • PhotoshopやPremire、ビデオエンコードなどCPU資源を大量に必要とするアプリケーションを使っていない。
  • 使っているPCのCPUがCore2Duo。低消費電力版ではあるが、Core2Duoの性能はAtomより優れている。性能が高い方が性能の変化がわかりにくいのではないかと推測する。
  • XPはインストール直後はとても軽い印象があるが、しばらく使っているとだんだん重くなってくる印象がある。これも体感なので根拠はないのだけれどVistaはその傾向が小さい気がする(最初から重いから?)

自分としての結論としては、高速化や応答性の向上というポイントでWindows7が明らかに優れているというはない。(あくまで自分の利用環境に限定した場合。)ただ、Windows7の基本機能についてまとめられたところを見ていると消費電力の低減やフォールト トレラント ヒープなど気になる機能もあるので、しばらく使ってみようと思う。