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

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

Common Lispを頑張る(45)

どうにも最近たるんでいます。
ただ、プログラミングしないと落ち着かないので最低眼の習慣は身についたようです。
「実践Common Lisp」やります。
今日から第10章、「数、文字、そして文字列」をやります。

Common Lispを構成する基本部品が関数、変数、マクロ、25種類の特殊オペレータだとすると、
プログラムの基本部品は使用するデータ構造だといえるそうです。
Common Lispには近代的な言語に見られるほとんどのデータ型が組みこまれています。
その上Lispでは関数もファーストクラスのデータ型なので、関数を変数に格納したり、
関数を引数や戻り値にできるし、実行時に作ることもできます。

上記で挙げたデータ型は序の口で、そもそも組み込み型が言語仕様で規定されているのは
プログラマが処理系にかかわらず安心して使えるようにするため、処理系と密に連携させたときに
効率的な実装にしやすいからです。Common Lispには組み込み型だけでなく、
新しいデータ型を独自に定義する方法やその操作を定義する方法、さらに組み込み型と連携させる方法もあります。

とりあえずは組み込み型から見ていきます。
組み込み型はそのデータ型を扱う関数によって定義されているので、
そのデータ型を学ぶにはそのデータ型を扱う関数について学べばいいそうです。

Common Lispの出自は数学にあり、そのため数学をする時に足を引っ張られる心配はありません。
Lispが数学だというと、Lispについて語られたこの有名なエッセイを思い出しますね。
"なぜ1950年代の言語が時代遅れにならないかという簡単な説明は、 それが技術じゃなくて数学だったということだ。"
Revenge of the Nerds

まあ、とりあえずLispの数には下記のような特徴があるそうです。
●限られたコンピュータのハードウェアで簡単に実装できる近似値よりも実際の数学の数に近い。
●2つの整数の章は切り詰められた値ではなく、正確な分数になる。
●浮動少数点数の型をいくつかサポートしている。
複素数もサポートしている。無理関数や複素領域における無理関数や超越関数の主値と分岐点も。

数値リテラル

色々な数値リテラルの書き方がありますが、忘れてはいけないのは読取器と評価器の仕事が分離していること。
読取器はテキストをLispのオブジェクトにするまでを担当し、
評価器はその受けとったオブジェクトのみを扱います。
ある型の数値にはテキストでの表現の方法がいくつもあり、それらは読取器で同じオブジェクトに変換されます。

CL-USER> 10
10
CL-USER> 20/2
10
CL-USER> #xa
10
CL-USER>

整数値のシンタックスは、省略可能な符号に続く1つ以上の数字。
分数は、省略可能な符号と分子を表す数字の並び、スラッシュ、分母を表す数字の並び。
有理数はすべて読み込まれるとともに正規化されます。
有理数は基数を10以外にできます。
●#b...2進数
●#o...8進数
●#x...16進数
●#nr...n進数(nは10進数の整数)

CL-USER> #b0101
5
CL-USER> #o567
375
CL-USER> #x89a
2202
CL-USER> #36rzzzz
1679615
CL-USER>

浮動少数点数も様々な方法で書くことができるようです。
有理数と違い、少数のシンタックスは読み込まれる数値の実際の型に影響します。
少数のサブタイプは、short,single,double,longの4つ。右にいくほどビットが多く精度の高い値となります。
浮動少数点数の基本的な書式は、省略可能な符号に続く、少数点を1つ含んでいてもいい10進の桁の列。
この数字の並びの後ろに指数部を表すマーカーが続くこともあります。
これは「コンピュータ向け科学記法」であると。
指数部を表すマーカーはs,f,d,lで、それぞれ少数のサブタイプを表しています。
eは標準の表現が使われることを意味するそうです。

CL-USER> 1.0
1.0
CL-USER> 1e0
1.0
CL-USER> 1d0
1.0d0
CL-USER> .1
0.1
CL-USER> 123e-1
12.3
CL-USER> 123e-2
1.23
CL-USER> 123d-1
12.3d0
CL-USER> 0.123e3
123.0
CL-USER> 123d23
1.23d25
CL-USER>

そして複素数です。複素数シンタックスでは、#cに実部と虚部を表す2つの数のリストを続けます。
実部と虚部は、両方とも有理数か、両方とも同じ種類の浮動少数点数となります。よって5種類。

有理数と浮動少数点数を混ぜて書くこともでき、その場合は有理数が適切な表現の浮動少数点数に変換されるそう。
違う種類の浮動少数点数を使えば、表現力が強い方に合わせられるということです。
ただし実部が有理数で虚部も有理数0の複素数はないと。それは有理数になるので。
そして虚部が0の複素数の型は、実部が表す浮動少数点数のオブジェクトとは常に区別されるそうです。

CL-USER> #c(2 1)
#C(2 1)
CL-USER> #c(2/3 3/4)
#C(2/3 3/4)
CL-USER> #c(2 1.0)
#C(2.0 1.0)
CL-USER> #c(2 1d0)
#C(2.0d0 1.0d0)
CL-USER> #c(1/2 1.0)
#C(0.5 1.0)
CL-USER> #c(3 0)
3
CL-USER> #c(3 0.0)
#C(3.0 0.0)
CL-USER>

基本的な数学

まだまだ数の話が続きます。基本的な代数演算は当然サポートされています。
演算を行う関数はLispの全ての種類の数値に使うことができるそうです。
これらの関数を3つ以上の引数に対して呼び出すのは、最初の2つの引数に対して関数を呼び出し、
その結果を残りの引数に対して呼び出すのと等価だそうです。
つまり、(+ 2 3 4)は、(+ (+ 2 3) 4)と同じということですね。
1つしか引数がなければ、+と*は与えられた引数をそのまま返し、

  • は符号を反転させ返し、/は逆数を返すそうです。
CL-USER> (+ #c(3 4) #c(3/4 5/6))
#C(15/4 29/6)
CL-USER> (+ 1)
1
CL-USER> (* -3)
-3
CL-USER> (- -3)
3
CL-USER> (/ -3)
-1/3
CL-USER> (- #c(4 9))
#C(-4 -9)
CL-USER> (/ #c(5 1d23))
#C(5.000000000000001d-46 -1.0000000000000001d-23)
CL-USER>

引数が全て同じ型の場合、結果も同じ型になります。
ただし有理数からなる複素数の演算で虚部が0になる場合だけは、結果は有理数になるそう。わかります。
引数の型が異なる場合、伝染するのは浮動少数点数と複素数であると。

/は結果を切り捨てないということです。そのため実数を切り捨てたり丸めたりして
整数に変換する方法が4つ用意されているそうです。
FLOOR...負の無限大の方向に切り捨てて、引数と等しいか小さい値のうち一番大きい値を返す。
CEILING...正の無限大の方向に切上げ、引数と等しいか大きい値のうち一番小さい値を返す。
TRUNCATE...0の方向に丸めこむ。
ROUND...最も近い整数に丸める。もし引数がちょうど中間値であった場合は偶数の方向に丸める。

実数同士の割り算における法、および余剰を返す関数はmodとremがあります。

これらの関数は正の商については等価ですが、負の商では違う結果になるそうです。

CL-USER> (mod 4 9)
4
CL-USER> (rem 4 9)
4
CL-USER> (mod 4 -9)
-5
CL-USER> (rem 4 -9)
4
CL-USER> (mod -9 4)
3
CL-USER> (rem -9 4)
-1
CL-USER> 

また、1+と1-という関数があります。引数に1を加算したり減算するもので、
incfやdecfとの違いはこれらが非破壊的関数だということです。

数値比較

関数=は数値を比較する述語です。型の違いを無視して数学的な値を比較します。

CL-USER> (= 1 1.0)
T
CL-USER> (eql 1 1.0)
NIL
CL-USER> (eq 1 1.0)
NIL
CL-USER> (equal 1 1.0)
NIL
CL-USER> (equalp 1 1.0)                 ;包括的な述語であるequalpは数値比較に=を使う
T
CL-USER>

逆に/=は全ての引数が異なる場合にtを返すそう。

CL-USER> (/= 1 2)
T
CL-USER> (/= 1 1.0)
NIL
CL-USER>

順序関数は、左から右に向って比較をします。

CL-USER> (> 1 2 3)
NIL
CL-USER> (< 1 2 3)
T
CL-USER> (>= 1 1 1)
T
CL-USER> (<= 1 2 1)
NIL
CL-USER>

複数の値のうち最小の数、最大の数を得るには、min,maxを使います。

CL-USER> (min 1 2 3)
1
CL-USER> (max 1 2 3)
3
CL-USER>

他にも便利な述語関数として、zerop,minusp,plusp,evenp,oddp等々があります。
名前で何をしてくれるのか明確で、関数名の最後にpがあることでこれらが真偽値を返すことがわかります。

高度な数学

これまでに出てきた関数は算術関数のうち初等的なものでした。
他にlog,exp,expt,sin,cons,tan,asin,acos,atan,sinh...様々な関数が
より高度な計算を行うためにサポートされているとのこと。

昔は数学が苦手だったのでどうも...。
プログラミングする上でいつまでも逃げていられないのでいつか復習します...。

文字

Common Lispの文字は数とは異なるオブジェクトだそう。
文字の読み込みに使うシンタックスは単純で、#\の後に望みの文字を続ければOKです。
なんの文字でもこの方法で文字として扱えますが、#\ とされても半角スペースだと人間にはわかりづらいです。
そこで、#\のあとに文字の名前を続ける方法が用意されています。
つまり、#\ と#\spaceは同じです。
他にはtab,page,rubout,linefeed,return,backspaceの名前が用意されているそうです。

文字比較

数における=と対になる、大文字小文字を比較する文字の比較関数はchar=。
大文字小文字の比較をしないバージョンは、char-equalです。

CL-USER> (char= #\a #\A)
NIL
CL-USER> (char-equal #\a #\A)
T
CL-USER> (char= #\a #\a #\a)
T
CL-USER> (char-equal #\a #\a #\A)
T
CL-USER>

それ以外の文字比較関数も同じ命名規約に従っています。
大文字小文字を区別する比較演算は数値の比較演算の名前の前にcharをつけたもので、
区別しないものはcharとハイフンのあとに比較演算をアルファベットで綴ったものとなっています。

文字列

Common Lispの文字列は実際には合成データ型だそうです。具体的には1次元の文字の配列。
というわけで文字列でできることは、シーケンスを扱う関数について扱う11章で説明してくれるそう。
とはいえ、文字列専用の関数もあり、そういったものについてはここで説明してくれるということです。

文字列リテラルはダブルクォートで括られたものです。
その中にはダブルクォートとバックスラッシュ以外の文字は含むことができ、
ダブルクォートとバックスラッシュもバックスラッシュでエスケープすることで含められます。
REPLでは読み込み可能なフォームで文字列が出力されます。
実際の文字列の内容を見るにはformatなどを使う必要があります。

CL-USER> "foo\"bazz\\"
"foo\"bazz\\"
CL-USER> (format t "foo\"bazz\\")
foo"bazz\
NIL
CL-USER> 

文字列比較

文字の比較関数と同じような命名規約の関数を使って文字列が比較できるそう。
とてもシンプルで、charだった部分をstringにしてあげればいいそう。

CL-USER> (string= "foo" "foo")
T
CL-USER> (string= "foo" "Foo")
NIL
CL-USER> (string-equal "foo" "Foo")
T
CL-USER> 

ただし数値や文字の比較関数と違って、2つの文字列でしか比較できないようです。
これはキーワード引数を取るようになっているからだそう。

start1,
end1, :start2, end2を引数にとり、それぞれの文字列の部分文字列の

開始インデックスと終了インデックスを指定できるとのこと。

CL-USER> (string= "a cat is cute" "this dog is cool" :start1 6 :end1 10 :start2 9 :end2 13)
T
CL-USER> 

上記では、" is "の部分を比較しています。endは省略すると開始から文字列の最後までを比較します。

文字列が異なる場合に真となる比較関数は、文字列に違いが見つかった最初のインデックスを返します。

CL-USER> (string/= "cat is cat" "cat is not cat")
7
CL-USER> 

そして、stirng<ですが...いまいちわからんです。

CL-USER> (string< "cat is cat" "cat is not cat")
7
CL-USER> (string< "catiscat" "catisnotcat")
5
CL-USER> (string> "catiscat" "catisnotcat")
NIL
CL-USER> (string< "alisp" "lisper")
0
CL-USER> 

冒頭部分が同じかつ、比較演算子が示すとおりの並びなら文字列に差異が出るインデックスを返し、
冒頭から違っていたら0を返すって感じでしょうか。
0でもnilとは違いますもんね。

調べました。...差異が決まったインデックスを返すのですね。
ただのtでなく、情報を上乗せしてくれる類の関数ですね。使いどころは限られそうですが無駄ではないと。

また長くなってしまいました。今日はここまで。