Common Lisp vs Haskell, chapter 1

Meta note

対象読者

初級〜中級CLer。 ないしCommon Lispに興味のあるHaskeller（いなさそうだなぁ）。

長文なのでお暇な時にどうぞ。

Introduction

論文を呼んでいると例題コードがHaskellで書かれているものに出会うことがままある。 筆者はCommon Lispにこそ精通している者であるが、Haskellについては無知蒙昧の徒である。 筆者は論文内の例題コードを、「色々難しいことをごちゃごちゃ言ったけど、実装するならこんな感じだよ」という、本文を理解するための補助輪のような存在と認識している。 その補助輪がチンプンカンプンであれば、内容の理解は一層難しくなる。 そこで論文を読めるようにするためにHaskellを読めるようにすることにした。

しかしながら大きな問題がある。 筆者はHaskellという言語をリスペクトしている。 Common Lispを書いていて不満に思う幾つかをHaskellは綺麗（？）に解決しているように見えるからだ。 とはいえ現時点で筆者はHaskellerになる気は毛頭ない。 筆者がHaskellを学ぶのは上記の通り、あくまで手段として、だ。 当然モチベーションは上がらない。 けして、やりたいことがすなわちHaskellというわけではないからだ。 入門書を読んでいても内容がさっぱり頭に入らない。 これではいけない。 そこでHaskellの各種機能を慣れ親しんだCommon Lispに置き換えながら学習することにした。

通常言語Xを学ぶ際に、言語Yに翻訳しながら解釈していくのは悪手である。 言語Xには言語Xの作法があり、言語Yのそれとは相容れない可能性があり、言語Yの知識が言語Xを学ぶことの足を引っ張ることになるのはよくあることである。 しかしながら今回に限りそれは当てはまらない。 筆者はけしてHaskellerになりたくて学び始めたわけではないからだ。 Ruby開発者のMatzがRubyプログラマでないのと同様に、いわばCommon Lisp上に埋め込みHaskellを作る気持ちで、すなわちHaskellという言語を学ぶ対象でなく作る対象と認識することで学習意欲を維持しようという作戦なわけだ。

さて、彼我の言語を見比べると、当然そこには差異がある。 そこで、HaskellになくCommon Lispに特徴的な機能については、Common Lispに無知蒙昧なHaskellerに解説するつもりで説明を試みた。 本稿の該当部分は初心者CLerにとってCommon Lisp学習の一助となろう。

また逆に、Common LispになくHaskellに特徴的な機能については、ライブラリを使って再現できそうなものは積極的にライブラリを使用し、ライブラリも見つからないような機能の中で簡単に実装できそうなものは自前で実装を試みた。 本稿の該当部分は中級CLerにとって興味深い内容となろう。 なお、本章に於けるハイライトは「中置演算を導入する（infix-math）」「リスト内包表記を導入する（incf-cl）」「無限リストを扱う（SERIES）」といったあたりだろうか。

Haskellerにとって本稿は、Common Lispの強力さの一端をうかがい知れる内容となっていようし、また、Haskellの強力さを再認識できる内容ともなっていよう。

上級CLerにとって得られるものは何もないかと思われるので、よほど暇でない限りはご自身のお仕事に戻って、どうぞ。

なお、今回採用したHaskell入門書はいわゆる「すごいH本」である。 本稿は「すごいH本」の内容をCommon Lispに翻訳しながらCLerがHaskellを学ぶその第一章である。

1

0

まずは簡単な四則演算から。

ghci > 2 + 15 17 ghci > 49 * 100 4900 ghci > 1892 - 1472 420 ghci > 5 / 2 2 . 5

cl-user> ( + 2 15 ) 17 cl-user> ( * 49 100 ) 4900 cl-user> ( - 1892 1472 ) 420 cl-user> ( / 5 2 ) 5/2 cl-user> ( float ( / 5 2 ) ) 2.5

Common Lispはオペレータが前置になっているという点以外はほとんど同じ。 唯一特徴的なのは / が分数を返すという点。 実数が欲しい場合は FLOAT に渡す必要がある。

Common Lispはオペレータを前置するシンタックスをサポートしているので、これら四則演算関数が二引数関数とは限らない。

cl-user> ( + ) 0 cl-user> ( - ) cl-user> ( * ) 1 cl-user> ( / ) cl-user> ( + 1 ) 1 cl-user> ( - 1 ) -1 cl-user> ( * 3 ) 3 cl-user> ( / 3 ) 1/3 cl-user> ( + 1 2 3 ) 6 cl-user> ( - 1 2 3 ) -4 cl-user> ( * 1 2 3 ) 6 cl-user> ( / 1 2 3 ) 1/6

括弧による優先順位の明示。

ghci > ( 50 * 100 ) - 4999 1 ghci > 50 * 100 - 4999 1 ghci > 50 * ( 100 - 4999 ) - 244950

cl-user> ( - ( * 50 100 ) 4999 ) 1 cl-user> ( * 50 ( - 100 4999 ) ) -244950

Common Lispは括弧が必須。 おかげで暗黙裏の優先順位を覚える必要がない。 これはトレードオフであり、好みの問題だ。 たとえ優先順位を覚える必要にかられようとも括弧を書きたくない人もおられよう。

なお、Common Lispではそのような構文を導入することもできる。

cl-user> ( infix-math:$ 50 * 100 - 4999 ) 1 cl-user> ( infix-math:$ 50 * ( 100 - 4999 ) ) -249950

ブール代数。

ghci > True && False False ghci > True && True True ghci > False || True True ghci > not False True ghci > not ( True && True ) False

cl-user> ( and t nil ) NIL cl-user> ( and t t ) T cl-user> ( or nil t ) T cl-user> ( not nil ) T cl-user> ( not ( and t t ) ) NIL

Common Lispは汎ブール（Generalized Boolean）をサポートしている。 すなわち、 NIL 以外は全てTrueとなる。

cl-user> ( not 1 ) NIL

Common Lispでは前置構文がサポートされているので、ブール代数のオペレータも２引数オペレータではない。

cl-user> (and) T cl-user> (or) NIL cl-user> (and t nil t) NIL

Common Lispは汎ブールがサポートされているので、返り値は BOOLEAN ではなく値となる。 すなわち、 AND なら最後に評価した節の返り値が、 OR なら最初にtrueに評価された節の返り値が式全体の返り値となる。

cl-user> (or nil 1 2) 1 cl-user> (and 1 2 3) 3

等号

ghci > 5 == 5 True ghci > 1 == 0 False ghci > 5 /= 5 False ghci > 5 /= 4 True ghci > "hello" == "hello" True ghci > 5 + "llama"

多くの言語で = は代入のための予約語であるため、等値比較には == を使うこととなる。 Common Lispは代入のためのキーワードを持たないので等値比較に = を使う。

cl-user> ( = 1 2 ) NIL cl-user> ( = 1 1 ) T

ただし = は数値比較のためのものであって、引数に数値以外が来るとエラーとなる。

cl-user> ( = 1 nil )

そのような場合には EQ のファミリーを使う。

cl-user> ( eql 1 nil ) NIL

EQ ファミリーの特徴は以下の通り。

EQ

シンボル同士の比較に用いる。 内部的にはポインタ比較なので高速に機能する。

EQL

シンボル、文字、整数の比較に用いる。

EQUAL

リスト、パス名、ケースセンシティブな文字列比較に用いる。

EQUALP

ケースインセンシティブな文字列比較、ハッシュテーブル、構造体、配列の比較に用いる。

文字の比較、文字列比較のための専用オペレータもある。 気をつけなければならないのは STRING のファミリーで、これは文字列ではなく、文字列指定子（STRING-DESIGNATOR）を受け付ける。 すなわち、文字でもシンボルでも機能する。

cl-user> ( char= #\a #\a ) T cl-user> ( char= #\a #\A ) NIL cl-user> ( char-equal #\a #\A ) T cl-user> ( char= #\a nil ) cl-user> ( string= "hoge" "hoge" ) T cl-user> ( string= "hoge" "HOGE" ) NIL cl-user> ( string-equal "hoge" "HOGE" ) T cl-user> ( string= #\a "a" ) T cl-user> ( string= 'hoge "HOGE" ) T cl-user> ( string= "0" 0 )

Common Lispの等値関数群は初心者にとって難解で悪名高いのだが、それに対する反論としてはSICPとCLtL2からの引用で事足りえよう。

参照透明性を一旦捨てると、計算オブジェクトが「同じ」であるとは何を意味するかの概念を形式的に捕らえるのは難しくなる。実はわれわれのプログラムがモデル化している実世界の「同じ」の意味もあまりはっきりしない。一般に二つの見かけ上同じなオブジェクトが本当に「同じもの」であるかは、一つのオブジェクトを変えてみて、もう一つのオブジェクトが同じように変わっているかを見て決める。しかし「同じ」オブジェクトを二度観測し、オブジェクトのある性質が一回目と二回目の観測で違っているということ以外に、あるオブジェクトが「変った」ことがどうして分るだろうか。つまり何か先験的な「同一」という概念なしに「変化」を決めることは出来ず、変化の効果を観測することなしに同一性を決めることは出来ない。 オブジェクトの等値性は、唯一定められた正しいアルゴリズムに基づいた概念ではない。等値の述語のもっともらしさは、ある特定のプログラムの文脈においてのみ判断できるものである。EQUALとEQUALP関数はいかなる型の引数も受け付け、名前も非常に汎用に聞こえるが、これらはすべてのアプリケーションに対してもっともらしいわけではない。これらの関数を使用するか否かという決定は、これらの関数の抽象的な性質よりも動作を規定したドキュメントによって成されるべきである。もし、ある場面でEQUALもEQUALPも不適切であると判明した場合には、プログラマはEQUALやEQUALPが「誤って動作する」と非難するのではなく、他の適切な操作関数を作り出すことが望まれる。

1.1

関数呼びだし。

ghci > succ 8 9

cl-user> ( 1+ 8 ) 9

ghci > min 9 10 9 ghci > min 3 . 4 3 . 2 3 . 2 ghci > max 100 101 101

cl-user> ( min 9 10 ) 9 cl-user> ( min 3.2 3.4 ) 3.2 cl-user> ( max 100 101 ) 101

Common Lispは前置構文をサポートしているので、 MAX 、 MIN 関数もまた２引数関数ではない。

cl-user> ( min 1 2 3 ) 1 cl-user> ( max 1 2 3 ) 3 cl-user> ( min 3 ) 3 cl-user> ( max 3 ) 3

括弧による優先順位の明示

ghci > succ 9 + max 5 4 + 1 16 ghci > ( succ 9 ) + ( max 5 4 ) + 1 16

cl-user> ( + ( 1+ 9 ) ( max 5 4 ) 1 ) 16 cl-user> ( infix-math:$ 1 + 9 + ( 5 max 4 ) + 1 ) 16

ghci > succ 9 * 10 100 ghci > succ ( 9 * 10 ) 91

Common Lispでは括弧は必須なので、曖昧性がない。 これはトレードオフである。

cl-user> ( * ( 1+ 9 ) 10 ) 100 cl-user> ( 1+ ( * 9 10 ) ) 91

ghci > div 92 10 9 ghci > 92 `div` 10 9

Common Lispで上記の DIV に相当する関数は全部で４つある。 各々あまりの丸め込み方が異なる。

cl-user> ( floor 92 10 ) 9 2 cl-user> ( ceiling 92 10 ) 10 -8 cl-user> ( truncate -1 2 ) 0 -1 cl-user> ( round 5 3 ) 2 -1

ghci > 92 `div` 10 9

Haskellでは簡単に関数を中置に出来るが、Common Lispにそのような機能はない。 数学系のものに限るなら、infix-mathが便利に使える。

cl-user> ( infix-math:$ 92 floor 10 ) 9 2

数学系のものに限らず何でも中置にできれば便利なこともあろうかと思われるので作ってみよう。

( defun |#%-reader| ( stream character number ) ( declare ( ignore character number ) ) ( let ( ( form ( read stream t t t ) ) ) ( rotatef ( car form ) ( cadr form ) ) form ) )

リードテーブルは以下の通り。

( named-readtables:defreadtable :secondfix-syntax ( :merge :standard ) ( :dispatch-macro-char #\# #\% #'|#%-reader| ) )

これで以下のように書ける。

cl-user> #% ( 92 floor 10 ) 9 2

実は本リーダマクロは、リードテーブルの名前が示す通り、厳密には中置ではなく第二値演算となっている。 よって、例えば以下のように書いても動く。

cl-user> #% ( 1 + 2 3 4 5 ) 15

非常に奇妙な仕様だが、 FIND などにキーワード引数を渡したい場合などに便利だ。

cl-user> #% ( "hoge" find ' ( "fuga" "bazz" ) :test #'string= ) NIL

1.2

doubleMe x = x + x

( defun double-me ( x ) ( + x x ) )

ghci > : l baby ghci > doubleMe 9 18 ghci > doubleMe 8 . 3 16 . 6

cl-user> ( load "baby" ) cl-user> ( double-me 9 ) 18 cl-user> ( double-me 8.3 ) 16.6

doubleUs x y = x * 2 + y * 2

( defun double-us ( x y ) ( + ( * x 2 ) ( * y 2 ) ) )

ghci > doubleUs 4 9 26 ghci > doubleUs 2 . 3 34 . 2 73 . 0 ghci > doubleUs 28 88 + doubleMe 123 478

cl-user> ( double-us 4 9 ) 26 cl-user> ( double-us 2.3 34.2 ) 73.0 cl-user> ( + ( double-us 28 88 ) ( double-me 123 ) ) 478

doubleSmallNumber x = if x > 100 then x else x * 2

( defun double-small-number ( x ) ( if ( > x 100 ) x ( * x 2 ) ) )

doubleSmallNumber' x = ( if x > 100 then x else x * 2 ) + 1

( defun double-small-number% ( x ) ( 1+ ( if ( > x 100 ) x ( * x 2 ) ) ) )

conanO'Brien = "It's a-me, Conan O'Brien!"

( defun |Connan O'Brien| ( ) ( progn "It's a-me, Conan O'Brien!" ) ) ( setf ( symbol-function '|Connan O'Brien| ) ( constantly "It's a-me, Conan O'Brien!" ) )

リテラル文字列を返す関数を書く時には注意が必要。 というのも、ドキュメンテーション文字列と解釈されうるからで、処理系依存でエラーになる場合がある。 よって上記の例では文字列を PROGN でラップしてBODYであることを明示している。

1.3

ghci > let lostNumbers = [ 4,8,15,16,23,42 ] ghci > lostNumbers [ 4,8,15,16,23,42 ]

cl-user> ( defvar *lost-numbers* ' ( 4 8 15 16 23 42 ) ) *LOST-NUMBERS* cl-user> *lost-numbers* ( 4 8 14 15 23 42 )

上記のようにスペシャル変数名の左右に * を付けるのはマナーであってルールではない。 別に * が無くても問題はない。

ghci > [ 1,2,3,4 ] ++ [ 9,10,11,12 ] [ 1,2,3,4,9,10,11,12 ] ghci > "hello" ++ " " ++ "world" "hello world" ghci > [ 'w' , 'o' ] ++ [ 'o' , 't' ] "woot"

Haskellに於いて文字列はリストだが、Common Lispに於いて文字列はベクタである。 よってリスト用関数は文字列に適用できない。 ただしリスト、文字列、ベクタをひっくるめて扱うシーケンスという型があり、それ用のオペレータも充実している。 上記 ++ に相当するのはCommon Lispでなら CONCATENATE である。 第一引数に返り値の型を明示する必要がある。

cl-user> ( concatenate 'list ' ( 1 2 3 4 ) ' ( 9 10 11 12 ) ) ( 1 2 3 4 9 10 11 12 ) cl-user> ( concatenate 'string "hello" " " "world" ) "hello world" cl-user> ( concatenate 'string "wo" ' ( #\o #\t ) ) "woot" cl-user> ( concatenate 'list "foo" "bar" ) ( #\f #\o #\o #\b #\a #\r )

リスト専用のものとしては APPEND がある。

cl-user> ( append ' ( 1 2 3 4 ) ' ( 9 10 11 12 ) ) ( 1 2 3 4 9 10 11 12 )

ghci > 'A' : " SMALL CAT" "A SMALL CAT" ghci > 5 : [ 1,2,3,4,5 ] [ 5,1,2,3,4,5 ]

Common Lispに於いて文字列はアトムであってリストではない。

cl-user> ( cons #\A " SMALL CAT" ) ( #\A . " SMALL CAT" )

Common Lispに於いて文字（CHARACTER）は列（SEQUENCE）ではない。 よって文字と文字列の連結は CONCATENATE ではできない。

cl-user> ( concatenate 'string #\A " SMALL CAT" )

UIOP:STRCAT を使うのがよい。

cl-user> ( uiop:strcat #\A " SMALL CAT" ) "A SMALL CAT"

CONS はリストをコンストラクトするためのオペレータである。

cl-user> ( cons 5 ' ( 1 2 3 4 5 ) ) ( 5 1 2 3 4 5 )

ghci > "Steve Buscemi" !! 6 'B' ghci > [ 9 . 4,33 . 2,96 . 2,11 . 2,23 . 25 ] !! 1 33 . 2

Haskellの !! はCommon Lispの ELT に相当する。

cl-user> ( elt "Steve Buscemi" 6 ) #\B cl-user> ( elt ' ( 9.4 33.2 96.2 11.2 23.25 ) 1 ) 33.2

ELT は型によるディスパッチを必要とするのでその分遅い。 より具体的な型に特定化されたアクセサがあるので、必要ならそれらを使う。

cl-user> ( char "Steve Buscemi" 6 ) #\B cl-user> ( schar "Steve Buscemi" 6 ) #\B cl-user> ( aref # ( 5 4 3 ) 0 ) 5 cl-user> ( svref # ( 5 4 3 ) 0 ) 5 cl-user> ( nth 0 ' ( 1 2 3 ) ) 1

ghci > let b = [ [ 1,2,3,4 ] , [ 5,3,3,3 ] , [ 1,2,2,3,4 ] , [ 1,2,3 ] ] ghci > b [ [ 1,2,3,4 ] , [ 5,3,3,3 ] , [ 1,2,2,3,4 ] , [ 1,2,3 ] ] ghci > b ++ [ [ 1,1,1,1 ] ] [ [ 1,2,3,4 ] , [ 5,3,3,3 ] , [ 1,2,2,3,4 ] , [ 1,2,3 ] , [ 1,1,1,1 ] ] ghci > [ 6,6,6 ] : b [ [ 6,6,6 ] [ 1,2,3,4 ] , [ 5,3,3,3 ] , [ 1,2,2,3,4 ] , [ 1,2,3 ] ] ghci > b !! 2 [ 1,2,2,3,4 ]

cl-user> ( defvar *b* ' ( ( 1 2 3 4 ) ( 5 3 3 3 ) ( 1 2 2 3 4 ) ( 1 2 3 ) ) ) *B* cl-user> *b* ( ( 1 2 3 4 ) ( 5 3 3 3 ) ( 1 2 2 3 4 ) ( 1 2 3 ) ) cl-user> ( append *b* ' ( ( 1 1 1 1 ) ) ) ( ( 1 2 3 4 ) ( 5 3 3 3 ) ( 1 2 2 3 4 ) ( 1 2 3 ) ( 1 1 1 1 ) ) cl-user> ( cons ' ( 6 6 6 ) *b* ) ( ( 6 6 6 ) ( 1 2 3 4 ) ( 5 3 3 3 ) ( 1 2 2 3 4 ) ( 1 2 3 ) ) cl-user> ( nth 2 *b* ) ( 1 2 2 3 4 )

ghci > [ 3,2,1 ] > [ 2,1,0 ] True ghci > [ 3,2,1 ] > [ 2,10,100 ] True ghci > [ 3,4,2 ] < [ 3,4,3 ] True ghci > [ 3,4,2 ] > [ 2,4 ] True ghci > [ 3,4,2 ] == [ 3,4,2 ] True

Common Lispにリストの比較関数は存在しない。 上記haskellコードと同様のセマンティクスは SOME を使うことで再現出来る。

cl-user> ( some #'> ' ( 3 2 1 ) ' ( 2 1 0 ) ) T cl-user> ( some #'> ' ( 3 2 1 ) ' ( 2 10 100 ) ) T cl-user> ( some #'< ' ( 3 4 2 ) ' ( 3 4 3 ) ) T cl-user> ( some #'> ' ( 3 4 2 ) ' ( 2 4 ) ) T cl-user> ( equal ' ( 3 4 2 ) ' ( 3 4 2 ) ) T

ghci > head [ 5,4,3,2,1 ] 5 ghci > tail [ 5,4,3,2,1 ] [ 4,3,2,1 ] ghci > last [ 5,4,3,2,1 ] 1 ghci > init [ 5,4,3,2,1 ] [ 5,4,3,2 ]

cl-user> ( car ' ( 5 4 3 2 1 ) ) 5 cl-user> ( cdr ' ( 5 4 3 2 1 ) ) ( 4 3 2 1 ) cl-user> ( first ' ( 5 4 3 2 1 ) ) 5 cl-user> ( rest ' ( 5 4 3 2 1 ) ) ( 4 3 2 1 ) cl-user> ( last ' ( 5 4 3 2 1 ) ) ( 1 )

Common Lispの LAST は最後のコンスを返すという仕様である。 要素が欲しいなら alexandria:lastcar を使う。

cl-user> ( alexandria:lastcar ' ( 5 4 3 2 1 ) ) 1

LASTCAR は名前が示す通りリスト専用である。 シーケンスに使いたいなら alexandria:last-elt が使える。

cl-user> ( alexandria:last-elt "foobar" ) #/r

Common Lispに於いて init に相当するのは BUTLAST である。

cl-user> ( butlast ' ( 5 4 3 2 1 ) ) ( 5 4 3 2 )

なお、 LAST と BUTLAST はオプショナルな引数を受け付ける。

cl-user> ( butlast ' ( 5 4 3 2 1 ) 2 ) ( 5 4 3 ) cl-user> ( last ' ( 5 4 3 2 1 ) 2 ) ( 2 1 )

ghci > head [ ] *** ERROR

cl-user> ( elt ( ) 0 ) cl-user> ( car ( ) ) NIL cl-user> ( cdr ( ) ) NIL cl-user> ( last ( ) ) NIL cl-suer> ( butlast ( ) ) NIL

ghci > length [ 5,4,3,2,1 ] 5

cl-user> ( length ' ( 5 4 3 2 1 ) ) 5

ghci > null [ 1,2,3 ] False ghci > null [ ] True

cl-user> ( null ' ( 1 2 3 ) ) NIL cl-user> ( null ( ) ) T

ghci > reverse [ 5,4,3,2,1 ] [ 1,2,3,4,5 ]

cl-user> ( reverse ' ( 5 4 3 2 1 ) ) ( 1 2 3 4 5 )

ghci > take 3 [ 5,4,3,2,1 ] [ 5,4,3 ] ghci > take 1 [ 3,9,3 ] [ 3 ] ghci > take 5 [ 1,2 ] [ 1,2 ] ghci > take 0 [ 6,6,6 ] [ ]

Haskellに於ける take はCommon Lispに於いて SUBSEQ が相当する。 ただし、いささか勝手が異なる。

cl-user> ( subseq ' ( 5 4 3 2 1 ) 0 3 ) ( 5 4 3 ) cl-user> ( subseq ' ( 3 9 3 ) 0 1 ) ( 3 ) cl-user> ( subseq ' ( 1 2 ) 0 5 ) cl-user> ( loop :for elt :in ' ( 1 2 ) :repeat 5 :collect elt ) ( 1 2 ) cl-user> ( subseq ' ( 6 6 6 ) 0 0 ) NIL

完全に同じセマンティクスのものが欲しいなら、SERAPEUMの TAKE が使える。 incf-clにも TAKE はあるが、リスト専用である。

cl-user> ( serapeum:take 3 ' ( 5 4 3 2 1 ) ) ( 5 4 3 ) cl-user> ( serapeum:take 1 ' ( 3 9 3 ) ) ( 3 ) cl-user> ( serapeum:take 5 ' ( 1 2 ) ) ( 1 2 ) cl-user> ( serapeum:take 0 ' ( 6 6 6 ) ) NIL

ghci > drop 3 [ 8,4,2,1,5,6 ] [ 1,5,6 ] ghci > drop 0 [ 1,2,3,4 ] [ 1,2,3,4 ] ghci > drop 100 [ 1,2,3,4 ] [ ]

Haskellの drop はCommon Lispに於いては NTHCDR が相当する。 ただし、名前の通り NTHCDR はリスト専用である。 文字列にも使いたいならSERAPEUMの DROP が使える。 incf-clにも DROP はあるが、リスト専用である。

cl-user> ( nthcdr 3 ' ( 8 4 2 1 5 6 ) ) ( 1 5 6 ) cl-user> ( nthcdr 0 ' ( 1 2 3 4 ) ) ( 1 2 3 4 ) cl-user> ( nthcdr 100 ' ( 1 2 3 4 ) ) NIL cl-user> ( nthcdr 3 "foobar" ) cl-user> ( serapeum:drop 3 "foobar" ) "bar"

ghci > maximum [ 1,9,2,3,4 ] 9 ghci > minimum [ 8,4,2,1,5,6 ] 1

Common Lispに上記相当の関数はない。 APPLY でなんとかする。

cl-user> ( apply #'max ' ( 1 9 2 3 4 ) ) 9 cl-user> ( max 1 9 2 3 4 ) 9 cl-user> ( apply #'min ' ( 8 4 2 1 5 6 ) ) 1 cl-user> ( min 8 4 2 1 5 6 ) 1

以下の sum 、 product も同様である。 この辺、余計な名前を覚えなくて良い分、Common Lispの方がスッキリしている印象ではあるが、Haskellの関数がデフォルトでカリー化されているという仕様を鑑みれば、まぁ、必要だよなぁ、とも思う。

ghci > sum [ 5,2,1,6,3,2,5,7 ] 31 ghci > product [ 6,2,1,2 ] 24 ghci > product [ 1,2,5,6,7,9,2,0 ] 0

cl-user> ( apply #'+ ' ( 5 2 1 6 3 2 5 7 ) ) 31 cl-user> ( + 5 2 1 6 3 2 5 7 ) 31 cl-user> ( apply #'* ' ( 6 2 1 2 ) ) 24 cl-user> ( * 6 2 1 2 ) 24 cl-user> ( apply #'* ' ( 1 2 5 6 7 9 2 0 ) ) 0 cl-user> ( * 1 2 5 6 7 9 2 0 ) 0

Haskellの elem はおよそCommon Lispの FIND に相当していると言える。

ghci > 4 `elem` [ 3,4,5,6 ] True ghci > 10 `elem` [ 3,4,5,6 ] False

cl-user> ( find 4 ' ( 3 4 5 6 ) ) 4 cl-user> ( find 10 ' ( 3 4 5 6 ) ) NIL

なお、 FIND は見つけたものを返すので NIL を見つけられない。 そのような場合、 POSITION を使う。

cl-user> ( find nil ' ( 1 nil 2 ) ) NIL cl-user> ( position nil ' ( 1 nil 2 ) ) 1

1.4

ghci > [ 1 .. 20 ] [ 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 ] ghci > [ 'a' .. 'z' ] "abcdefghijklmnopqrstuvwxyz" ghci > [ 'K' .. 'Z' ] "KLMNOPQRSTUVWXYZ"

Common Lispに上記機能はない。 Ord という型と、後継者関数 succ の存在が大きい。 これはHaskellの綺麗さの一旦であろうと思う。 文字列がリストだというのも一役かってそう。 ただ、Haskellerの中でも文字列がリストなのは間違いだという意見もあるらしく、どうなるものやら。

整数のレンジを求めるだけならINCF-CLの RANGE が使える。

cl-user> ( incf-cl:range 1 20 ) ( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 )

文字のレンジから文字列を求めるのは、それ用のユーティリティも見当たらず、必要なら自作するしかない。

( defun char-range ( x y ) ( let* ( ( code-x ( char-code x ) ) ( code-y ( char-code y ) ) ( string ( make-string ( 1+ ( - code-y code-x ) ) ) ) ) ( loop :for n :upfrom code-x :to code-y :for i :upfrom 0 :do ( setf ( char string i ) ( code-char n ) ) ) string ) ) cl-user> ( char-range #\a #\z ) "abcdefghijklmnopqrstuvwxyz" cl-user> ( char-range #\K #\Z ) "KLMNOPQRSTUVWXYZ"

ghci > [ 2,4 .. 20 ] [ 2,4,6,8,10,12,14,16,18,20 ] ghci > [ 3,6 .. 20 ] [ 3,6,9,12,15,18 ]

Common Lispでは LOOP マクロが割と強力なので、 LOOP でなんとかするCLerが多いのではないかと思われる。

cl-user> ( loop :for e :upfrom 2 :to 20 :by 2 :collect e ) ( 2 4 6 8 10 12 14 16 18 20 ) cl-user> ( incf-cl:range 2 2 20 ) ( 2 4 6 8 10 12 14 16 18 20 ) cl-user> ( loop :for e :upfrom 6 :to 20 :by 3 :collect e ) ( 6 9 12 15 18 ) cl-user> ( incf-cl:range 6 3 20 ) ( 6 9 12 15 18 )

ghci > [ 1,2,4,8,16 .. 100 ] ;; Does not work .

cl-user> ( loop :for i :upfrom 1 :for expt = ( expt 2 i ) :while ( < expt 100 ) :collect expt ) ( 2 4 8 16 32 64 )

ghci > [ 20,19 .. 1 ] [ 20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1 ] ghci > [ 20 .. 1 ] [ ]

icf-clの RANGE は降順をサポートしていない。

cl-user> ( loop :for i :downfrom 20 :to 1 :collect i ) ( 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 ) cl-user> ( loop :for i :upfrom 20 :to 1 :collect i ) NIL

ghci > [ 13,26 .. 24 * 13 ] [ 13,26,39,52,65,78,91,104,117,130,143,156,169,182,195,208,221,234,247,260,273,286,299,312 ] ghci > take 24 [ 13,26 .. ] [ 13,26,39,52,65,78,91,104,117,130,143,156,169,182,195,208,221,234,247,260,273,286,299,312 ]

無限リストを扱うなら SERIES が使える。

cl-user> ( loop :for i :upfrom 13 :by 13 :repeat 24 :collect i ) ( 13 26 39 52 65 78 91 104 117 130 143 156 169 182 195 208 221 234 247 260 273 286 299 312 ) cl-user> ( series:collect ( series:scan-range :from 13 :by 13 :length 24 ) ) ( 13 26 39 52 65 78 91 104 117 130 143 156 169 182 195 208 221 234 247 260 273 286 299 312 )

ghci > take 10 ( cycle [ 1,2,3 ] ) [ 1,2,3,1,2,3,1,2,3,1 ] ghci > take 12 ( cycle "LOL " ) "LOL LOL LOL "

cl-user> ( incf-cl:take 10 ( incf-cl:cycle ' ( 1 2 3 ) ) ) ( 1 2 3 1 2 3 1 2 3 1 ) cl-user> ( series:collect ( series:subseries ( series:series 1 2 3 ) 0 10 ) ) ( 1 2 3 1 2 3 1 2 3 1 ) cl-user> ( series:collect 'string ( series:subseries ( series:series . #. ( coerce "LOL " 'list ) ) 0 12 ) ) "LOL LOL LOL " cl-user> ( coerce ( incf-cl:take 12 ( incf-cl:cycle ( coerce "LOL " 'list ) ) ) 'string ) "LOL LOL LOL "

なお、効率はSERIESの方が良い。

( lambda ( ) ( series:collect 'string ( series:subseries ( series:series . #. ( coerce "LOL " 'list ) ) 0 12 ) ) ) ( compile nil * ) ( time ( funcall * ) ) ( lambda ( ) ( coerce ( incf-cl:take 12 ( incf-cl:cycle '#. ( coerce "LOL " 'list ) ) ) 'string ) ) ( compile nil * ) ( time ( funcall * ) )

マクロ展開のオーバーヘッドを効率に入れないよう、コンパイルしてから計測している点注意。 上記のコストは完全に実行時のコストである。

ghci > take 10 ( repeat 5 ) [ 5,5,5,5,5,5,5,5,5,5 ]

cl-user> ( series:collect ( series:subseries ( series:series 5 ) 0 10 ) ) ( 5 5 5 5 5 5 5 5 5 5 )

ghci > replicate 3 10 [ 10,10,10 ]

Haskellの replicate はCommon Lispに於いては MAKE-LIST に相当する。

cl-user> ( make-list 3 :initial-element 10 ) ( 10 10 10 )

ghci > [ 0 . 1,0 . 3 .. 1 ] [ 0 . 1,0 . 3,0 . 5,0 . 7,0 . 8999999999999999,0 . 999999999999999 ]

cl-user> ( loop :for f :upfrom 0.1 :to 1 :by 0.2 :collect f ) ( 0.1 0.3 0.5 0.7 0.900000004 )

1.5

ghci > [ x * 2 | x <- [ 1 .. 10 ] ] [ 2,4,6,8,10,12,14,16,18,20 ]

リスト内包表記にはINCF-CLの LC が使える。 素の LOOP マクロでも多くをまかなえる。

cl-user> ( loop :for i :upfrom 1 :to 10 :collect ( * i 2 ) ) ( 2 4 6 8 10 12 14 16 18 20 ) cl-user> ( incf-cl:lc ( * x 2 ) ( incf-cl:<- x ( incf-cl:range 1 10 ) ) ) ( 2 4 6 8 10 12 14 16 18 20 )

単純な例では LOOP の方が効率がいい。

( lambda ( ) ( loop :for i :upfrom 1 :to 10 :collect ( * i 2 ) ) ) ( compile nil * ) ( time ( funcall * ) ) ( lambda ( ) ( incf-cl:lc ( * x 2 ) ( incf-cl:<- x ( incf-cl:range 1 10 ) ) ) ) ( compile nil * ) ( time ( funcall * ) )

ghci > [ x * 2 | x <- [ 1 .. 10 ] , x * 2 >= 12 ] [ 12,14,16,18,20 ] ghci > [ x | x <- [ 50 .. 100 ] , x `mod` 7 == 3 ] [ 52,59,66,73,80,87,94 ]

cl-user> ( loop :for x :upfrom 1 :to 10 :for y = ( * x 2 ) :when ( >= y 12 ) :collect y ) ( 12 14 16 18 20 ) cl-user> ( incf-cl:lc ( * x 2 ) ( incf-cl:<- x ( incf-cl:range 1 10 ) ) ( >= ( * x 2 ) 12 ) ) ( 12 14 16 18 20 ) cl-user> ( loop :for x :upfrom 50 :to 100 :when ( = 3 ( mod x 7 ) ) :collect x ) ( 52 59 66 73 80 87 94 ) cl-user> ( incf-cl:lc x ( incf-cl:<- x ( incf-cl:range 50 100 ) ) ( = ( mod x 7 ) 3 ) ) ( 52 59 66 73 80 87 94 )

boomBangs xs = [ if x < 10 then "BOOM!" else "BANG!" | x <- xs, odd x ]

( defun boom-bangs ( xs ) ( loop :for x :in xs :when ( oddp x ) :collect ( if ( < x 10 ) "BOOM!" "BANG!" ) ) )

ghci > boomBangs [ 7 .. 13 ] [ "BOOM!" , "BOOM!" , "BANG!" , "BANG!" ]

cl-user> ( boom-bangs ( loop :for i :upfrom 7 :to 13 :collect i ) ) ( "BOOM!" "BOOM!" "BANG!" "BANG!" )

ghci > [ x | x <- [ 10 .. 20 ] ,x /= 13, x /= 15, x /= 19 ] [ 10,11,12,14,16,17,18,20 ]

cl-user> ( loop :for i :upfrom 10 :to 20 :unless ( find i ' ( 13 15 19 ) ) :collect i ) ( 10 11 12 14 16 17 18 20 ) cl-user> ( incf-cl:lc x ( incf-cl:<- x ( incf-cl:range 10 20 ) ) ( not ( find x ' ( 13 15 19 ) ) ) ) ( 10 11 12 14 16 17 18 20 )

ghci > [ x + y | x <- [ 1,2,3 ] , [ 10,100,1000 ] ] [ 11,101,1001,12,102,1002,13,103,1003 ]

素の LOOP で書くのが苦しくなってきた。

cl-user> ( loop :for i :in ' ( 1 2 3 ) :nconc ( loop :for j :in ' ( 10 100 1000 ) :collect ( + i j ) ) ) ( 11 101 1001 12 102 1002 13 103 1003 ) cl-user> ( alexandria:map-product #'+ ' ( 1 2 3 ) ' ( 10 100 1000 ) ) ( 11 101 1001 12 102 1002 13 103 1003 ) cl-user> ( incf-cl:lc ( + x y ) ( incf-cl:<- x ' ( 1 2 3 ) ) ( incf-cl:<- y ' ( 10 100 1000 ) ) ) ( 11 101 1001 12 102 1002 13 103 1003 )

複雑な例になると効率差がなくなってくる。

( lambda ( ) ( loop :for i :in ' ( 1 2 3 ) :nconc ( loop :for j :in ' ( 10 100 1000 ) :collect ( + i j ) ) ) ) ( compile nil * ) ( time ( funcall * ) ) ( lambda ( ) ( alexandria:map-product #'+ ' ( 1 2 3 ) ' ( 10 100 1000 ) ) ) ( compile nil * ) ( time ( funcall * ) ) ( lambda ( ) ( incf-cl:lc ( + x y ) ( incf-cl:<- x ' ( 1 2 3 ) ) ( incf-cl:<- y ' ( 10 100 1000 ) ) ) ) ( compile nil * ) ( time ( funcall * ) )

ghci > [ x * y | x <- [ 2,5,10 ] , y <- [ 8,10,11 ] , x * y > 50 ] [ 55,80,100,110 ]

cl-user> ( remove-if-not ( lambda ( x ) ( > x 50 ) ) ( alexandria:map-product #'* ' ( 2 5 10 ) ' ( 8 10 11 ) ) ) ( 55 80 100 110 ) cl-user> ( loop :for x :in ' ( 2 5 10 ) :nconc ( loop :for y :in ' ( 8 10 11 ) :for z = ( * x y ) :when ( > z 50 ) :collect z ) ) ( 55 80 100 110 ) cl-user> ( incf-cl:lc ( * x y ) ( incf-cl:<- x ' ( 2 5 10 ) ) ( incf-cl:<- y ' ( 8 10 11 ) ) ( > ( * x y ) 50 ) ) ( 55 80 100 110 )

ghci > let nouns = [ "hobo" , "frog" , "pope" ] ghci > let adjectives = [ "lazy" , "grouchy" , "scheming" ] ghci > [ adjective ++ " " ++ noun | adjective <- adjectives, noun <- nouns ] [ "lazy hobo" , "lazy frog" , "lazy pope" , "grouchy hobo" , "grouchy frog" , "grouchy pope" , "scheming hobo" , "scheming frog" , "scheming pope" ]

cl-user> ( let ( ( nouns ' ( "hobo" "frog" "pope" ) ) ( adjectives ' ( "lazy" "grouchy" "scheming" ) ) ) ( alexandria:map-product ( lambda ( adjective noun ) ( concatenate 'string adjective " " noun ) ) adjectives nouns ) ) ( "lazy hobo" "lazy frog" "lazy pope" "grouchy hobo" "grouchy frog" "grouchy pope" "scheming hobo" "scheming frog" "scheming pope" ) cl-user> ( incf-cl:lc ( concatenate 'string adjuctive " " noun ) ( incf-cl:<- adjuctive ' ( "lazy" "grouchy" "scheming" ) ) ( incf-cl:<- noun ' ( "hobo" "frog" "pope" ) ) ) ( "lazy hobo" "lazy frog" "lazy pope" "grouchy hobo" "grouchy frog" "grouchy pope" "scheming hobo" "scheming frog" "scheming pope" )

length' xs = sum [ 1 | _ <- xs ]

Common Lispに於いて ' はマクロ文字なので通常シンボル名などには使えない。 代わりに % を使うのが作法として定着している。

( defun length% ( xs ) ( loop :for i :in xs :sum 1 ) )

removeNonUppercase st = [ c | c <- st, c `elem` [ 'A' .. 'Z' ] ] ghci > removeNonUppercase "Hahaha! Ahahaha!" "HA" ghci > removeNonUppercase "IdontLIKEFROGS" "ILIKEFROGS"

( defun remove-non-uppercase ( string ) ( remove-if-not #'upper-case-p string ) ) cl-user> ( remove-non-uppercase "Hahaha! Ahahaha!" ) "HA"

ghci > let xxs = [ [ 1,3,5,2,3,1,2,4,5 ] , [ 1,2,3,4,5,6,7,8,9 ] , [ 1,2,4,2,1,6,3,1,3,2,3,6 ] ] ghci > [ [ x | x <- xs, even x ] | xs <- xxs ] [ [ 2,2,4 ] , [ 2,4,6,8 ] , [ 2,4,2,6,2,6 ] ]

cl-user> ( defvar *xxs* ' ( ( 1 3 5 2 3 1 2 4 5 ) ( 1 2 3 4 5 6 7 8 9 ) ( 1 2 4 2 1 6 3 1 3 2 3 6 ) ) ) *XXS* cl-user> ( mapcar ( lambda ( xs ) ( remove-if #'oddp xs ) ) *xxs* ) ( ( 2 2 4 ) ( 2 4 6 8 ) ( 2 4 2 6 2 6 ) )

1.6

ghci > ( 1, 3 ) ( 1,3 ) ghci > ( 3, 'a' , "hello" ) ( 3,'a', "hello" ) ghci > ( 50, 50 . 4, "hello" , 'b' ) ( 50,50 . 4, "hello" , 'b' )

Common LispにHaskellのタプルに相当する機能はない。 リストで代用が可能だろうか。

cl-user> ' ( 1 . 3 ) ( 1 . 3 ) cl-user> ' ( 3 . ( #\a . ( "hello" . nil ) ) ) ( 3 #\a "hello" ) cl-user> ' ( 50 . ( 50.4 . ( "hello" . ( #\b . nil ) ) ) ) ( 50 50.4 "hello" #\b )

型を明示的に指定したければ DEFTYPE を使う。

( deftype int-tuple ( ) ' ( cons integer integer ) ) ( deftype int-triple ( ) ' ( cons integer ( cons integer ( cons integer null ) ) ) ) cl-user> ( typep ' ( 1 . 2 ) 'int-tuple ) T cl-user> ( typep ' ( 1 . ( 2 . nil ) ) 'int-tuple ) NIL

ghci > fst ( 8, 11 ) 8 ghci > fst ( "Wow" , False ) "Wow" ghci > snd ( 8, 11 ) 11 ghci > snd ( "Wow" , False ) False

cl-user> ( car ' ( 8 . 11 ) ) 8 cl-user> ( car ' ( "Wow" . false ) ) "Wow" cl-user> ( cdr ' ( 8 . 11 ) ) 11 cl-user> ( cdr ' ( "Wow" . false ) ) FALSE

上記の FALSE はただのシンボルであり、True値である点要注意。

ghci > zip [ 1,2,3,4,5 ] [ 5,5,5,5,5 ] [ ( 1,5 ) , ( 2,5 ) , ( 3,5 ) , ( 4,5 ) , ( 5,5 ) ] ghci > zip [ 1 .. 5 ] [ "one" , "two" , "three" , "four" , "five" ] [ ( 1, "one" ) , ( 2, "two" ) , ( 3, "three" ) , ( 4, "four" ) , ( 5,five ) ] ghci > zip [ 5,3,2,6,2,7,2,5,4,6,6 ] [ "im" , "a" , "turtle" ] [ ( 5, "im" ) , ( 3, "a" ) , ( 2, "turtle" ) ] ghci > zip [ 1 .. ] [ "apple" , "orange" , "cherry" , "mango" ] [ ( 1, "apple" ) , ( 2, "orrange" ) , ( 3, "cherry" ) , ( 4, "mango" ) ]

Haskellの zip はCommon Lispに於いては MAPCAR でおよそ再現できる。

cl-user> ( mapcar #'cons ' ( 1 2 3 4 5 ) ' ( 5 5 5 5 5 ) ) ( ( 1 . 5 ) ( 2 . 5 ) ( 3 . 5 ) ( 4 . 5 ) ( 5 . 5 ) ) cl-user> ( mapcar #'cons ( incf-cl:range 1 5 ) ' ( "one" "two" "three" "four" "five" ) ) ( ( 1 . "one" ) ( 2 . "two" ) ( 3 . "three" ) ( 4 . "four" ) ( 5 . "five" ) ) cl-user> ( mapcar #'cons ' ( 5 3 2 6 2 7 2 5 4 6 6 ) ' ( "im" "a" "turtle" ) ) ( ( 5 . "im" ) ( 3 . "a" ) ( 2 . "turtle" ) ) cl-user> ( series:collect ( series:map-fn 'list #'cons ( series:scan-range :from 1 ) ( series:scan ' ( "apple" "orrange" "cherry" "mango" ) ) ) ) ( ( 1 . "apple" ) ( 2 . "orrange" ) ( 3 . "cherry" ) ( 4 . "mango" ) )

ghci > let rightTriangles = [ ( a,b,c ) | c <- [ 1 .. 10 ] , a <- [ 1 .. c ] , b <- [ 1 .. a ] , a ^ 2 + b ^ 2 == c ^ 2, a + b + c == 24 ] ghci > rightTriangles [ ( 8,6,10 ) ]

cl-user> ( loop :for c :upfrom 1 :to 10 :nconc ( loop :for a :upfrom 1 to c :nconc ( loop :for b :upfrom 1 to a :when ( and ( = ( + ( expt a 2 ) ( expt b 2 ) ) ( expt c 2 ) ) ( = 24 ( + a b c ) ) ) :collect ( list a b c ) ) ) ) ( ( 8 6 10 ) ) cl-user> ( incf-cl:lc ( list a b c ) ( incf-cl:<- c ( incf-cl:range 1 10 ) ) ( incf-cl:<- a ( incf-cl:range 1 c ) ) ( incf-cl:<- b ( incf-cl:range 1 a ) ) ( = ( infix-math:$ a infix-math:^ 2 + b infix-math:^ 2 ) ( infix-math:$ c infix-math:^ 2 ) ) ( = ( infix-math:$ a + b + c ) 24 ) ) ( ( 8 6 10 ) )

本稿では例題コードをREPLに貼り付ければそのまま動くように、必ずパッケージ名をプリフィックスにつけて記してある。 そのため、大変冗長であり、煩わしく見えるかもしれない。 実際に使う場合は各種パッケージをUSEすることになるかと思われるので、プリフィックスを付ける必要はなくなるであろう。

プリフィックスをつけない場合、上記コードは以下のようになる。