ITコンサルの日常

ITコンサル会社に勤務する普通のITエンジニアの日常です。

「ANSI Common Lisp」2章読了

Lispを学ぶついでに、Haskellの復習もやってしまおうという企画(?)にしました。
が、どこまで出来るのかは不明です。。(と逃げを打っておく)

リストから3番目の要素を取り出す

thirdって関数があるらしい。便利。

(third '(1 2 3))
(third '(1 2))
(third '(a b c))
(third '(a b))
(third '())

結果はこう。

* 
3
* 
NIL
* 
C
* 
NIL
* 
NIL

Haskellでも同じことをやろうと思ったが、標準でthird相当の関数がないようだったので作ってみた。

main = do print $ third [1,2,3]
          print $ third [1,2]
          print $ third ["a","b","c"]
          print $ third ["a","b"]
          print $ third "abc"
          print $ third "ab"
          print $ third ([] :: [Int])
          print $ third ""

-- 3番目の要素を取得する関数
third::[a] -> [a]
third x = if length(x) > 2 then [last $ take 3 x] else []

(2008/08/01 追記)
const ()さんより、

length を使うと無限リストに対応できなくなります。

とのアドバイスがあり、

third::[a] -> [a]
third xs = take 1 (drop 2 xs)

とした方が良いとのアドバイスがありました。


結果はこう。

[3]
[]
["c"]
[]
"c"
""
[]
""

下から二行目の、空リストに型情報を付加しているのは、これをやらないと以下のようなエラーが出るためです。

third.hs:7:10:
    Ambiguous type variable `a' in the constraint:
      `Show a' arising from use of `print' at third.hs:7:10-14
    Probable fix: add a type signature that fixes these type variable(s)

多分、(単なる?)空リストだとShowのインスタンスではないため、printの定義

print :: Show a => a -> IO ()

と合わないからでしょうね。

再帰の例

あるリストにある要素が含まれるかどうかを調べる関数。
見つかった場合、見つかった要素以降のリストを返す。
見つからなかった場合、NILを返す。
という仕様らしい。
ちなみに標準のmemberも試してみた。

(defun our-member (obj lst)
  (if (null lst)
    nil
    (if (eql (car lst) obj)
       lst
       (our-member obj (cdr lst)))))


(our-member 'b '(a b c))
(our-member 'z '(a b c))
(member 'b '(a b c))
(member 'z '(a b c))

結果はこう。

* 
OUR-MEMBER
* 
(B C)
* 
NIL
* 
(B C)
* 
NIL
* 

同じことをやっぱりHaskellでもやってみる。
入ってるかどうかだけを調べるならelemが使えそう。

main = do print $ elem 'b' "abc"
          print $ elem 'z' "abc"

結果はこう。

True
False

見つかった場合、見つかった要素以降のリストを返す。
を実現するなら、spanが使えそう。

main = do print $ snd $ span (/= 'b') "abc"
          print $ snd $ span (/= 'z') "abc"

結果はこう。

"bc"
""

入出力

printfみたいなことが出来るらしい。

(format t "~A plus ~A equals ~A.~%" 2 3 (+ 2 3))

結果はこう。

* 2 plus 3 equals 5.
NIL
* 

Haskellではどうかというと、GHCにはText.Printfというモジュールがあります。

import Text.Printf

main = putStrLn $ printf "%d plus %d equals %d." (2::Int) (3::Int) ((2::Int) + (
3::Int))

結果はこう。

2 plus 3 equals 5.

単に2とか3とか書いてもダメなのですよね。文字列は大丈夫なようですが、うまく型推論(?)が出来ないみたいです。

変数

letでローカル変数の定義が出来ますよってこと。

(let ((x 1) (y 2))
  (+ x y))

結果はこう。

* 
3
* 

xとyはletの中でしか有効でないので、letの外で参照しようとすると、

Error in KERNEL::UNBOUND-SYMBOL-ERROR-HANDLER:  the variable X is unbound.

みたいなエラーが出ます。


ちなみにHaskellにもletあります。

main = let x = 1
           y = 2
       in print $ (x + y)

結果はこう。

3

グローバル変数

defparameterで定義する。再代入も可能らしい。

(defparameter *glob* 99)

(format t "~A" *glob*)

(defparameter *glob* 199)

(format t "~A" *glob*)

結果はこう。

* 
*GLOB*
* 99
NIL
* 
*GLOB*
* 199
NIL
* 

グローバル定数を定義するdefconstantもある。こちらは再代入不可。

(defconstant *glob* 99)

(format t "~A" *glob*)

(defconstant *glob* 199)

(format t "~A" *glob*)

結果はこう。

* 
*GLOB*
* 99
NIL
* Error in batch processing:

Error in function C::%%DEFCONSTANT:  Constant *GLOB* being redefined.

再代入を避けるには、すでにバインド済みかどうかboundpでテストする。

(defconstant *glob* 99)

(format t "~A" *glob*)

(if (boundp '*glob*)
  (format t "~A" *glob*)
  (defconstant *glob* 199))

結果はこう。

* 
*GLOB*
* 99
NIL
* 99
NIL
* 

Haskellには再代入という概念がないので、同様のコードは書けません。
しかし、こういうことが出来てしまうと、Lispは純粋な関数型言語というよりは、関数型言語でもあり、手続き型言語でもあるということのようですね。

代入

ローカル変数も上書き出来てしまうらしい。凶暴だなあ。

(let ((n 10))
  (setf n 2)
  n)

結果はこう。

* 
2
* 

関数プログラミング

関数プログラミングでは、setfのような副作用的な操作を原則的に避ける。初めのうちは、望ましさ云々はさておき、どのようにしたらそれが可能かも想像しにくいであろう。どうやって返り値だけでプログラムを作成できるのだろう、という疑問も当然ありうるに違いない。

Haskell界隈では"参照透明性"とかいう言葉で語られることですが、Lispにおいても参照透明性は確保した方が良いという話のようです。
Haskellと違って強制ではないので、意識する必要がありますが。


本書中では、remove関数が元のリストから削除してしまうわけではないということで説明してます。

(setf x '(a b c d))
(setf y (remove 'b x))
(format t "x = ~A" x)
(format t "y = ~A" y)

結果はこう。

* Warning:  Declaring X special.

(A B C D)
* Warning:  Declaring Y special.

(A C D)
* x = (A B C D)
NIL
* y = (A C D)
NIL
* 

反復

手続き言語におけるwhile文のようなものが作れる。

(defun show-squares (start end)
  (do ((i start (+ i 1)))
      ((> i end) 'done)
    (format t "~A ~A~%" i (* i i))))

(show-squares 2 5)

結果はこう。

* 
SHOW-SQUARES
* 2 4
3 9
4 16
5 25
DONE
* 

あえてRubyで書けばこんな感じ?

def show_squares(st, ed)
  i = st
  until i > ed
    print i, " ", i*i, "\n"
    i += 1
  end
end

show_squares(2, 5)

Haskellではどうだろう。

-- aからbまでのリストを作る
makeListAToB::Int -> Int -> [Int]
makeListAToB a b = [a..b]

-- aからbまでの2乗を表示する
showSquares::Int -> Int -> [String]
showSquares a b = map formatToSquare $ makeListAToB a b

-- 数値を受け取って2乗を表示するように整形する
formatToSquare::Int -> String
formatToSquare n = (show n) ++ " " ++ (show (n*n))

main = putStrLn $ unlines $ showSquares 2 5

繰り返しっていう構文はない(リスト内包表記がそれに近いのか?)ので、
リストを作成してmapを適用するようにしました。


ちなみにこのHaskellにおけるmapは、Lispにおけるdolistに相当するらしい。
このdolistを使ってshow-squaresを書き直してみる。

(defun show-squares (lst)
  (dolist (obj lst)
    (format t "~A ~A~%" obj (* obj obj))))

(show-squares '(2 3 4 5))

結果はこう。

* 
SHOW-SQUARES
* 2 4
3 9
4 16
5 25
NIL
* 

最後にNILが返ってくるのが違うくらい。
'doneを返したければ、こうすればいい。(そんな必要ないと思うけど)

(defun show-squares (lst)
  (dolist (obj lst)
    (format t "~A ~A~%" obj (* obj obj)))
  'done)

(show-squares '(2 3 4 5))

オブジェクトとしての関数

関数は、シンボル、ストリング、リストなどと同様に通常のオブジェクト(Lispのデータ)である。

ということで、関数をオブジェクトとして扱うfunction関数、その短縮系の#'、apply関数で呼び出しを行う説明がされているので、インタラクティブに実行してみた。

* (function +)

#<Function + {10127409}>
* #'+

#<Function + {10127409}>
* (apply #'+ '(1 2 3))

6
* (+ 1 2 3)

6
* 

なんとなくJavaScriptを思い出したので、書いてみる。

<html>
<script language = "JavaScript">
function sum()
{
  result = 0;
  for(i=0; i<arguments.length; i++)
  {
    result += arguments[i];
  }

  return result;
}

sumObj = sum;
alert("sum = " + sumObj.apply(null, [1,2,3]));
</script>
</html>

引数は配列じゃないといけないのですね。やられた。


あとlambdaの説明書きもあり。

((lambda (x) (+ x 100)) 1)

100を足す関数を定義して、すぐ1を引数にして呼び出してる。
結果はこう。

* 
101
* 

Haskellでは、

main = print $ (\n -> n + 100) 1

ですね。

データ型

Common Lispに組み込まれた型には、下位ー上位の階層がある。オブジェクトは常に複数の型をもっている。たとえば、27という数は、下位の型から挙げてゆけば、固定数(fixnum)、整数(integer)、有理数(rational)、実数(real)、数(number)、アトム(atom)、tにそれぞれ該当する。

Lispオブジェクト指向っぽくもあるというような説明に受け取れました。


なお、typep関数で型に属するかどうかテストできる。

(defun typetest(obj type)
  (if (typep obj type)
    (format t "~A belongs to ~A" obj type)
    (format t "~A not belongs to ~A" obj type)))
  
(typetest 27 'fixnum)
(typetest 27 'integer)
(typetest 27 'rational)
(typetest 27 'real)
(typetest 27 'number)
(typetest 27 'atom)
(typetest 27 't)
(typetest 27 'string)

結果はこう。

* 
TYPETEST
* 27 belongs to FIXNUM
NIL
* 27 belongs to INTEGER
NIL
* 27 belongs to RATIONAL
NIL
* 27 belongs to REAL
NIL
* 27 belongs to NUMBER
NIL
* 27 belongs to ATOM
NIL
* 27 belongs to T
NIL
* 27 not belongs to STRING
NIL
* 

というわけで一通り読み終わったので練習問題にチャレンジする。