Chapter 07

【付録】他言語との対応

TAKIZAWA Yozo
TAKIZAWA Yozo
2021.12.28に更新

Python

Pythonはマルチパラダイムのプログラミング言語であり,手続き型やオブジェクト指向型だけでなく,関数型のパラダイムも導入されています.ただし,Pythonはプログラムコードの読みやすさを特に重視しており,手順やデータの構造化という観点では,代入を活用したプログラミングスタイルの方が親和性が高いと言えます.第一章で述べたメモリ領域の使用効率と併せ,Pythonにおける関数型のプログラミングスタイルは,部分的・局所的な導入に留めた方が良いでしょう.ここでは,Python 3について概説します.

ラムダ式と三項演算子

Pythonでもラムダ式による関数規則の定義が可能であり,次のように,キーワードlambdaを用いて定義します.数式は,数学と同じ中置記法と演算子の優先順位が利用できます.

Python3
lambda x, y: x + y * 2

この関数規則に,たとえば引数x10,引数y20として評価する場合は,関数(引数, 引数, ...)の形式で表現します.ただし,ラムダ式の範囲を示すため,lambdaの式全体も括弧で括る必要があります(以降,Pythonの対話モードで式の評価と結果を示します).

Python3
>>> (lambda x, y: x + y * 2)(10, 20)
50

述語表現や判断分岐をラムダ式と合わせて表現する場合は,手続き型のif文ではなく,次に示す,式として評価可能な三項演算子を用いることになります.

条件式が真の時に評価される式 if 条件式 else 条件式が偽の時に評価される式
Python3
>>> (lambda x: x * -1 if x < 0 else x)(30)
30
>>> (lambda x: x * -1 if x < 0 else x)(-30)
30

本書で用いたfpLISPと同じく,引数にはラムダ式も与えることができ,再帰も可能です.ただし,ラムダ式のみで改行すると式として評価を終えてしまうため,評価させたい範囲を括弧で調整する必要があります.

Python3
>>> (lambda fib: fib(21, 0, 1))(
...   (lambda u: u(u))
...     (lambda u:
...       lambda n, a, b:
...         a if n == 0
...         else u(u)(n - 1, b, a + b)))
10946

もうひとつの問題として,Pythonではメモリ領域の使用効率の悪化を避けるため,再帰呼び出しに回数制限を設けています.呼び出し回数は増やすこともできますが,先のメモリ効率の趣旨を踏まえ,再帰呼び出しが膨大になることが予想される場合は,再帰表現を避けた方が良いでしょう.

Python3
>>> (lambda u: u(u))(lambda u: u(u))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <lambda>
  File "<stdin>", line 1, in <lambda>
  File "<stdin>", line 1, in <lambda>
  [Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded

リストと辞書型

再帰表現を避けた関数型スタイルとして,Pythonには様々な組込機能が用意されています.ここでは,リストと辞書型を扱う例を紹介します.

Pythonではデータ構造としてリストが利用できますが,このリストは,正確には,再帰データ構造をもつ連結リストではなく,要素としてあらゆる型のデータを用いることができる配列構造です.このため,Pythonでいうリスト処理は,添字や演算子を用いた配列処理が基本です.リスト表現も多くのプログラミング言語の配列と同じく,角括弧とカンマ区切りで構成されます.添字も角括弧で指定しますが,Pythonでは,負の値を用いた後方参照と,コロンを用いた範囲参照が可能です.

Python3
>>> [10, 20, 30, 40, 50]
[10, 20, 30, 40, 50]
>>> [10, 20, 30, 40, 50][0]
10
>>> [10, 20, 30, 40, 50][-1]
50
>>> [10, 20, 30, 40, 50][1:-1]
[20, 30, 40]
>>> [10, 20, 30, 40, 50][1:]
[20, 30, 40, 50]
>>> [10, 20, 30, 40, 50][:-1]
[10, 20, 30, 40]
>>> [10, 20, 30] + [30, 40, 50]
[10, 20, 30, 30, 40, 50]
>>> [10, 20, 30] * 3
[10, 20, 30, 10, 20, 30, 10, 20, 30]

Pythonにはリスト内包表記があり,第四章にて登場したmapに相当する評価結果を得ることができます.次は,リスト[10, 20, 30, 40, 50]の各要素をxとした時,要素ごとにx + 1を計算してあらためてリストとして出力する表現です.

Python3
>>> [x + 1 for x in [10, 11, 12, 13, 14]]
[11, 12, 13, 14, 15]

リスト内包表記は,再帰や繰り返しを伴うことがなければ,ラムダ式を用いずに様々な関数規則を各要素に直接適用することができます.この関数規則には,次のように,三項演算子も含まれます.

Python3
>>> [(lambda a: a * -1 if a < 0 else a)(x) for x in [-3, -2, -1, 0, 1, 2, 3]]
[3, 2, 1, 0, 1, 2, 3]
>>> [x * -1 if x < 0 else x for x in [-3, -2, -1, 0, 1, 2, 3]]
[3, 2, 1, 0, 1, 2, 3]

また,規則的な要素をもつリストを生成したり利用したりできる様々な組込関数等があります.次は,rangeを用いて0から9までの整数が並ぶリストを生成している例です(引数が10であることに注意して下さい).

Python3
>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

上記で用いているlistは,リストに変換する関数ですが,リスト内包表記でinを用いて呼び出す場合は不要となります.

Python3
>>> [x + 10 for x in range(10)]
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

第四章で登場した連想リストに近いデータ構造として,Pythonには辞書型があります.コロンで構成した『キー: 値』のペア構造を,波括弧とカンマ区切りで表現します.なお,ペア構造は集合扱いであり,リストと異なり,ペア構造の間に順番は存在しません.

Python3
>>> {1: 10, 2: 20, 3: 30, 4: 40, 5: 50}
{1: 10, 2: 20, 3: 30, 4: 40, 5: 50}

キーを指定して値を得るには,assocのような関数は必要なく,リストと同じように角括弧の添字にキーを指定します.

Python3
>>> {1: 10, 2: 20, 3: 30, 4: 40, 5: 50}[2]
20
>>> 30 + {1: 10, 2: 20, 3: 30, 4: 40, 5: 50}[3]
60
>>>

キーと値には,数値だけでなく,シングルクォーテーションかダブルクォーテーションで括った文字列も利用できます.値だけでなくキーにも文字列が利用できることに注意して下さい.

Python3
>>> {'a': 'alpha', 'b': 'beta', 'r': 'gamma'}['b']
'beta'

JavaScript

JavaScriptも,前節のPythonと同じく,マルチパラダイムのプログラミング言語ですが,利用者側のWebブラウザで実行されるスクリプト言語からスタートした背景もあって,参照透過性をより重視する傾向があります.以下,Pythonの節とほぼ同じ構成で概説します.また,実行例は,JavaScript実行環境であるNode.jsの対話モードで示します.

ラムダ式,三項演算子

JavaScriptには変数宣言の機能が複数あり,そのうちのconstは,初期値を用いて宣言すると値を変更できない,すなわち,定数に名前を付ける役割を果たします.

JavaScript
> const x;
const x;
      ^

Uncaught SyntaxError: Missing initializer in const declaration
> const x = 10;
undefined
> x
10
> x = 20;
Uncaught TypeError: Assignment to constant variable.
> const x = 20;
Uncaught SyntaxError: Identifier 'x' has already been declared
> const f = (x) => x + 100;
undefined
> f(x)
110
> f = (x) => x + 200;
Uncaught TypeError: Assignment to constant variable.
> const f = (x) => x + 200;
Uncaught SyntaxError: Identifier 'f' has already been declared

上記において,fの初期値である(x) => x + 100はラムダ式で,JavaScriptで最も簡便な表記方法であるアロー関数式で表現されています.引数変数は括弧とカンマ区切りで表現されますが,引数がひとつの時は括弧は省略できます.関数規則本体は=>で区切り,関数規則における数式は,数学と同じ中置記法と演算子の優先順位が利用可能です.ラムダ式自体は必ずしも名前を付ける必要はなく,括弧で括ることで関数名と同様に使用できます.

JavaScript
> (x => x + 100)(10)
110
> ((x, y) => x + y * 2)(10, 20)
50
>

述語表現や判断分岐をラムダ式と合わせて表現する場合は,前節のPythonと同じく三項演算子を用いますが,表現のための記号や述語の位置が異なります.

条件式 ? 条件式が真の時に評価される式 : 条件式が偽の時に評価される式
JavaScript
> (x => x < 0 ? x * -1 : x)(30)
30
> (x => x < 0 ? x * -1 : x)(-30)
30
>

本書の例に登場した再帰表現も可能ですが,Pythonのラムダ式と同じく,ラムダ式のみで改行すると式として評価を終えてしまうため,評価させたい範囲を括弧で調整する必要があります.

JavaScript
> (fib => fib(21, 0, 1))(
... (u => u(u))
... (u =>
..... (n, a, b) =>
.....   n == 0 ? a : u(u)(n - 1, b, a + b)))
10946

Pythonと同じく,JavaScriptでも再帰呼び出しに回数制限があります.

JavaScript
> (u => u(u))(u => u(u))
Uncaught RangeError: Maximum call stack size exceeded
    at REPL18:1:18
    at REPL18:1:18
    at REPL18:1:18
    at REPL18:1:18
    at REPL18:1:18
    at REPL18:1:18
    at REPL18:1:18
    at REPL18:1:18
    at REPL18:1:18

配列と連想配列

本書で登場した連結リストと連想リストに相当するデータ構造として,配列と連想配列がありますが,こちらもそれぞれPythonのリストと辞書型と同じで,表記も同じです.ただし,配列要素を後方や範囲で参照する場合は,sliceメソッドを用います.後方参照の場合も配列が返ってくることに注意して下さい.

JavaScript
> [10, 20, 30, 40, 50]
[ 10, 20, 30, 40, 50 ]
> [10, 20, 30, 40, 50][0]
10
> [10, 20, 30, 40, 50].slice(-1)
[ 50 ]
> [10, 20, 30, 40, 50].slice(-1)[0]
50
> [10, 20, 30, 40, 50].slice(1,-1)
[ 20, 30, 40 ]
> [10, 20, 30, 40, 50].slice(1)
[ 20, 30, 40, 50 ]
> [10, 20, 30, 40, 50].slice(0,-1)
[ 10, 20, 30, 40 ]

また,配列を結合するメソッドとしてconcatがあります.

JavaScript
> [10, 20, 30].concat([30, 40, 50])
[ 10, 20, 30, 30, 40, 50 ]

配列の各要素にラムダ式を適用した評価結果を得る場合は,メソッドmapを用います.

JavaScript
> [10, 11, 12, 13, 14].map(x => x + 1)
[ 11, 12, 13, 14, 15 ]

連想配列は次の通り,Pythonと全く同じ表現となります.ただし,連想配列の表現に直接添字を指定する方法だけでは利用できないため,式として組み合わせるか,括弧で括る必要があります.

JavaScript
> 30 + {1: 30, 2: 20, 3: 30, 4: 40, 5: 50}[3];
60
> ({'a': 'alpha', 'b': 'beta', 'r': 'gamma'})['b'];
'beta'

畳み込み処理

JavaScriptの配列はreduceメソッドが使用できますが,次の通り,指定するラムダ式の引数構成があらかじめ決められています.

配列.reduce((それまでに求めた値, 現在参照している値) => 式, 初期値)

このreduceを用いて配列の要素の合計を求める式は次のようになります.

JavaScript
> [10, 20, 30, 40, 50].reduce((r, c) => r + c, 0)
150

また,メソッドmapに似た例は次の通りです.

JavaScript
> [10, 20, 30, 40, 50].reduce((r, c) => r.concat(c + 1), [])
[ 11, 21, 31, 41, 51 ]

Ruby

Rubyもマルチパラダイムのスクリプト系言語ですが,歴史的な経緯から,オブジェクト指向パラダイムを中心に据えつつ関数型プログラミングも取り入れている,という傾向があります.たとえばラムダ式については,オブジェクト指向寄りの,処理の集まりであるブロックをメソッドに渡すための仕組みから,関数型に特化したブロック化の機能のみを取り出したものと言えます.結果として,ブロックのための表記は複数存在します.以降,Rubyの対話環境であるirbの簡易プロンプト表示による実行例を交えて概説します.

ラムダ式と三項演算子

次は,アロー演算子を用いたラムダ式の記述方法です.アロー演算子->が最初に記述されること,ブロックは式を波括弧で括って構成すること,引数の値の指定は角括弧のカンマ区切りを用いることに注意して下さい.

Ruby
>> -> x {x + 100}[10]
=> 110
>> -> x, y {x + y * 2}[10, 20]
=> 50

三項演算子はJavaScriptと同じく,次の形式で表現します.

条件式 ? 条件式が真の時に評価される式 : 条件式が偽の時に評価される式
Ruby
>> -> x { x < 0 ? x * -1 : x }[30]
=> 30
>> -> x { x < 0 ? x * -1 : x }[-30]
=> 30

再帰表現のラムダ式の例は次の通りです.PythonやJavaScriptと同じく,ラムダ式として評価させたい範囲を括弧で調整する必要があり,また,再帰呼び出しには回数制限があります.

Ruby
>> -> fib { fib[21, 0, 1] }[
?>   -> u { u[u] }[
?>     -> u {
?>       -> n, a, b {
?>         n == 0 ? a : u[u][n - 1, b, a + b]
>>       }
>>     }
>>   ]
>> ]
=> 10946
Ruby
>> -> u { u[u] }[-> u { u[u] }]
Traceback (most recent call last):
       16: from (irb):27:in `block in irb_binding'
       15: from (irb):27:in `block in irb_binding'
       14: from (irb):27:in `block in irb_binding'
       13: from (irb):27:in `block in irb_binding'
       12: from (irb):27:in `block in irb_binding'
       11: from (irb):27:in `block in irb_binding'
       10: from (irb):27:in `block in irb_binding'
        9: from (irb):27:in `block in irb_binding'
        8: from (irb):27:in `block in irb_binding'
        7: from (irb):27:in `block in irb_binding'
        6: from (irb):27:in `block in irb_binding'
        5: from (irb):27:in `block in irb_binding'
        4: from (irb):27:in `block in irb_binding'
        3: from (irb):27:in `block in irb_binding'
        2: from (irb):27:in `block in irb_binding'
        1: from (irb):27:in `block in irb_binding'
SystemStackError (stack level too deep)

配列とハッシュ

Rubyにおける連結リスト相当は配列となります.PythonやJavaScriptと同じく,要素に様々なデータ構造を扱うことができ,また,0から始まる英数の添字で参照することが可能です.次の例の通り,Pythonと同じく負の値による後方参照が利用できますが,範囲参照の表記・結果はPythonのそれとは微妙に異なります.配列要素の結合は+concatメソッドを用います.

Ruby
>> [10, 20, 30, 40, 50]
=> [10, 20, 30, 40, 50]
>> [10, 20, 30, 40, 50][0]
=> 10
>> [10, 20, 30, 40, 50][-1]
=> 50
>> [10, 20, 30, 40, 50][1..-1]
=> [20, 30, 40, 50]
>> [10, 20, 30] + [30, 40, 50]
=> [10, 20, 30, 30, 40, 50]
>> [10, 20, 30].concat([30, 40, 50])
=> [10, 20, 30, 30, 40, 50]

配列の各要素への同じ関数規則の適用については,mapメソッドを用います.次の例のように,上記とはまた別のブロック表現(波括弧の冒頭に||を用いて引数変数を指定)であることに注意して下さい.

Ruby
>> [10, 11, 12, 13, 14].map {|x| x + 1}
=> [11, 12, 13, 14, 15]

連想リストに違いRubyのデータ構造としてはハッシュがあり,キーと値を=>で結び付けたカンマ区切りの要素の集合を波括弧で括って表現します.

Ruby
>> 30 + {1=>10, 2=>20, 3=>30, 4=>40, 5=>50}[3]
=> 60
>> {'a'=>'alpha', 'b'=>'beta', 'r'=>'gamma'}['b']
=> "beta"

畳み込み処理

配列に対してreduceinjectとも)メソッドが使用できますが,mapと同じブロック表現となる他,JavaScriptと同じく引数構成が決まっています.

配列.reduce(初期値){|それまでに求めた値, 現在参照している値| 式}

このreduceを用いて配列の要素の合計を求める式は次のようになります.

Ruby
>> [10, 20, 30, 40, 50].reduce(0){|r, c| r + c}
=> 150

また,メソッドmapに似た例は次の通りです.

Ruby
>> [10, 20, 30, 40, 50].reduce([]){|r, c| r.concat([c + 1])}
=> [11, 21, 31, 41, 51]

Haskell

Haskellは,純関数型プログラミング言語とされていますが,同時に,強い静的型付き言語でもあります[1].これは,関数型パラダイムによって,メモリ領域における値参照時の不具合を減らすだけでなく,プログラム実行時に必要なメモリ領域を的確に確保することで,更に不具合をなくし,また,メモリ使用効率の向上を目的としているためです[2].そのこともあって,Haskellは基本的に,マシン語に変換して実行する,コンパイラ方式の言語処理系として実装されており,安全かつ高速な処理が期待できます.関数などのまとまった単位でコンパイル・実行する[3]ことで,対話型で実行できる仕組みもあります.

この仕様の結果,たとえば,Haskellでも無名関数であるラムダ式を利用できるものの,事前に無限ループに陥るとわかる式は実行されず,第三章で登場したラムダ式によるループ式も実行されずエラーとなります.このことは,判断分岐を組み込んだ再帰処理も実行されないことを意味します.このため,再帰が必要な関数規則は必ず名前付けを行い,その名前を用いて自己の関数規則を呼び出すことになります[4]

ここでは,Haskell処理系としてGHC(Glasgow Haskell Compiler)およびその対話インタフェースであるGHCiを用いて評価・実行結果を示します.

ラムダ式とif

Haskellでは,\がラムダ式を表す記号となり[5],引数は空白区切りで示します.関数規則本体とは->で区切り,関数規則における数式は,数学と同じ中置記法と演算子の優先順位が利用可能です.

Haskell
\ x y -> x + y * 2

このラムダ式の引数x10,引数y20を適用して評価する場合も,引数への値を空白区切りで表現します.ただし,ラムダ式の範囲を示すため,ラムダ式全体を括弧で括る必要があります.

Haskell
Prelude> (\ x y -> x + y * 2) 10 20
50

先にHaskellは強い静的型付きと述べましたが,扱う値の型が明白の場合は,型の指定を省略することができます.この付録文書では,省略できる記述のみを扱っています.

再帰処理に関数規則の名前付けが必要なのは既に述べた通りですが,Haskellの遅延評価[6]を利用して,次のように定義される最も単純な不動点コンビネータfixを用いると,引数に自身を参照する名前を用いたラムダ式が再帰呼び出し処理として評価可能です.

Haskell
Prelude> fix = \ f -> f (fix f)

例として,第三章に登場した,21番目のフィボナッチ数を求めるラムダ式の評価結果を次に示します[7]

Haskell
Prelude> fix (\ fib -> \ n a b -> if n == 0 then a else fib (n - 1) b (a + b)) 21 0 1
10946

上記の例で既に登場しましたが,Haskellでは判断分岐のために,次の構文で構成されるif式が利用できます.

if 条件式 then 条件式が真の時に評価される式 else 条件式が偽の時に評価される式

連結リストと連想リスト

Haskellのリストは,連結リストとして構成されますが,LISP系言語のそれとは異なり,ペア構造によるものではなく,専用のリスト型として構成されます.また,多くのコンパイラ系プログラミング言語の配列で見られるように,要素は全て同じ型でなければならず,角括弧とカンマ区切りの表記です.先頭の要素はhead,先頭の要素以外の残りの要素をもつリストはtailで参照でき,演算子:でリストの先頭に要素を加えたリストを生成することができます.[]fpLISPnilに相当します.

Haskell
Prelude> head [10,20,30,40,50]
10
Prelude> tail [10,20,30,40,50]
[20,30,40,50]
Prelude> 10 : [20,30,40,50]
[10,20,30,40,50]
Prelude> 10 : 20 : 30 : 40 : 50 : []
[10,20,30,40,50]

なお,Haskellの文字列は『文字のリスト』として扱われ,値としての文字列はダブルクォーテーション,文字はシングルクォーテーションで括ります.

Haskell
Prelude> head "abcde"
'a'
Prelude> tail "abcde"
"bcde"
Prelude> 'a' : "bcde"
"abcde"
Prelude> 'a' : 'b' : 'c' : 'd' : 'e' : []
"abcde"

リストの各要素に同じ関数規則を適用する場合は,mapが利用できますが,同様の機能をもつ関数は比較的容易に定義可能です[8]

Haskell
Prelude> map (\ x -> x + 1) [10,11,12,13,14]
[11,12,13,14,15]
Prelude> fix = \ f -> f (fix f)
Prelude> fix (\ map1 -> \ f x -> if x == [] then [] else f (head x) : map1 f (tail x)) (\ x -> x + 1) [10,11,12,13,14]
[11,12,13,14,15]

連想リスト相当のデータ型は,高速処理を目的に開発されたハッシュテーブルのライブラリモジュールを用いるのが一般的ですが,小規模のキーと値のペア構造だけならば,連結リストの要素にタプルを用いて実現するのが簡単です.タプルは,異なる型の要素を括弧とカンマ区切りで表現しますが,Haskellの場合,ペアのタプルは独立したデータ型として扱われ,1番目の要素はfst,2番目の要素はsndで参照できます.

Haskell
Prelude> fst ('a',"alpha")
'a'
Prelude> snd ('a',"alpha")
"alpha"
Prelude> fst (head [('a',"alpha"),('b',"beta"),('r',"gamma")])
'a'
Prelude> snd (head (tail [('a',"alpha"),('b',"beta"),('r',"gamma")]))
"beta"

ところで,Haskellでは,括弧で括って区別する方法の他に,$を用いて範囲を区別する方法が利用できる場合があり,次の通り,より見やすく表現することが可能です.

Haskell
Prelude> fst $ head [('a',"alpha"),('b',"beta"),('r',"gamma")]
'a'
Prelude> snd $ head $ tail [('a',"alpha"),('b',"beta"),('r',"gamma")]
"beta"

次は,上記と同じデータ構造で表現した連想リストをキー検索している例です[9]

Haskell
Prelude> fix = \ f -> f $ fix f
Prelude> fix (\ assoc -> \ k v -> if v == [] then -1 else if k == (fst $ head v) then (snd $ head v) else (assoc k $ tail $ v)) 'c' [('a',10),('b',20),('c',30),('d',40),('e',50)]
30
Prelude> fix (\ assoc -> \ k v -> if v == [] then -1 else if k == (fst $ head v) then (snd $ head v) else (assoc k $ tail $ v)) 'x' [('a',10),('b',20),('c',30),('d',40),('e',50)]
-1

畳み込み処理

Haskellには標準で連結リストに対する畳み込み処理のための組込関数があり,本書で解説したものと同じ関数としてfoldrがあります.引数構成および指定するラムダ式の引数構成は次の通りです.

foldr (\ 現在参照している値 それまでに求めた値 -> 式) 初期値 リスト

次は,foldrを用いたリスト内の要素の合計を求める処理です.演算子は数学と同じ中置記法であるためそのままでは指定できませんが,括弧で括ることで,ラムダ式と同じ前置記法で用いることが可能です.

Haskell
Prelude> foldr (\ c r -> c + r) 0 [10,20,30,40,50]
150
Prelude> (+) 10 20
30
Prelude> foldr (+) 0 [10,20,30,40,50]
150

foldrを用いたmapに似た例は次の通りです.

Haskell
Prelude> foldr (\ c r -> (c + 1) : r) [] [10,20,30,40,50]
[11,21,31,41,51]

LISP系言語

ここでは,本書で用いているfpLISPと,LISP系言語仕様の主流であるScheme,Common Lisp,Emacs Lispとの対比について述べています.なお,LISP系言語は関数表現が特徴のひとつではありますが,関数型パラダイムを中心としたプログラミング言語ではないことに注意して下さい[10]

Scheme

SchemeはLISP系言語のひとつで,本書のfpLISPはこのSchemeからほとんどの言語仕様を採用しています.組込関数の名前や真偽値の表現が異なるだけですので,本書のfpLISP記述は,少し書き換えるだけでScheme処理系でもそのまま評価・実行が可能です.

fpLISP Scheme
lambda if quote
cons car cdr
+ - *
(ほぼ同じ)
eq eq?
lt <
/ quotient
% remainder
t #t
nil 偽は#f
空リストは(quote ())

なお,リスト処理の例で登場したmapassocに相当する関数規則については,ほぼ同じ処理を行う関数mapassqが標準で利用できます.assqについては,値ではなく,検索キーと値のペア構造を返します.

Scheme
(map (lambda (x) (+ x 1)) (quote (10 20 30 40 50)))
(11 21 31 41 51)
Scheme
(assq (quote c) (quote ((a . 10) (b . 20) (c . 30) (d . 40) (e . 50))))
(c . 30)

Common Lisp

Common LispもLISP系言語のひとつで,真偽値t nilの扱いはfpLISPでもほぼそのまま取り入れています.ただし,lambdaの扱いは大きく異なり,名前を付けて呼び出したり戻り値として得られたりする場合,すなわち,評価結果としてのラムダ式は,funcallで文字通り呼び出す必要があります[11]

CommonLisp
((lambda (x) (if (< x 0) (* x -1) x)) -30)
30
CommonLisp
(funcall ((lambda (f)
            (lambda (y)
              (funcall f 10 y)))
          (lambda (x y)
            (+ x (* y 2))))
         20)
50

また,記号列に用いる英語アルファベットの大文字・小文字は区別されず,プログラムコード作成時の小文字は,評価結果では全て大文字表示となります[12]

CommonLisp
(quote hoge)
HOGE
CommonLisp
(cons (quote a) (quote b))
(A . B)

その他の対応は次の通りです.

fpLISP Common Lisp
if quote t nil
cons car cdr
eq + - *
(ほぼ同じ)
lt <
(/ X Y) (/ X Y)または(float (/ X Y))
% rem

Common Lispについても,mapassoc相当の標準関数があり,それぞれmapcarassoc(同じく検索キーと値のペア構造を返す)が対応します.

CommonLisp
(mapcar (lambda (x) (+ x 1)) (quote (10 20 30 40 50)))
(11 21 31 41 51)
CommonLisp
(assoc (quote c) (quote ((a . 10) (b . 20) (c . 30) (d . 40) (e . 50))))
(C . 30)

fold相当の標準関数であるreduceもありますが,本書と同じ処理を行わせるには,初期値を示すオプションを指定する必要があります.

CommonLisp
(reduce
  (lambda (c r) (+ c r))
  (quote (10 20 30 40 50))
  :initial-value 0)
150
CommonLisp
(reduce (lambda (c r) (cons (+ c 1) r))
        (quote (10 20 30 40 50))
        :initial-value nil
        :from-end t)
(11 21 31 41 51)
CommonLisp
(reduce (lambda (c r) (cons c r))
        (quote (10 20 30 40 50))
        :initial-value (quote (101 102 103 104))
        :from-end t)
(10 20 30 40 50 101 102 103 104)

Emacs Lisp

GNU Emacsは,機能拡張が可能なテキストエディタですが,実質的には,LISP系言語Emacs Lispを用いた統合開発環境と呼べるほどの言語処理機能を提供します.起動後の*scratch*バッファでは,記述したEmacs Lispコードを評価し,結果を確認することができます.次は,Common Lispの例で登場した,連結リストの各要素への関数適用の評価および結果確認の様子です.

EmacsLisp
(mapcar (lambda (x) (+ x 1)) (quote (10 20 30 40 50))) ※この式の直後にCtrlキー+Jキー押す
(11 21 31 41 51) ※次行に評価結果が表示される

上記の例のように,Emacs Lispの構文や組込関数の多くはCommon Lispと同じです.ただし,ラムダ式における名前参照の仕組みが異なること,大文字と小文字は区別されること,本書で登場したもの以外の機能で一部違いが見られることなどに注意する必要があります[13].最も異なるのは名前参照の仕組みですが,こちらは,次の一文を実行した後であれば,Common Lispと同じ仕組みとなります[14]

EmacsLisp
(setq lexical-binding t)
脚注
  1. 静的型付きとは,関数の入力(引数)と出力(戻り値)がどのようなデータ型,または,データ構造であるかを,プログラム上で明確に定めておくことです. ↩︎

  2. 型付きラムダ計算を数学的なモデルとしている,という側面もあります. ↩︎

  3. JIT(Just in Time)コンパイラと呼ばれています. ↩︎

  4. また,プログラム外部の文字・数値入出力の類は,関数型パラダイムの枠組みから外れた別の仕組みを用意し,その仕組みを経由して実際の入出力を行うというスタイルをとっています.より正確には,それぞれの仕組みを型として定義して利用するという考え方で,そのような型はモナドと呼ばれています. ↩︎

  5. 日本語環境では円マークとなる場合があります. ↩︎

  6. 先に引数を評価してその結果を関数に入力するのではなく,関数が引数の評価結果を必要とすることになった時に引数を評価する,という仕組みです.if式の『条件式が真の時になった時のみ,真の時の式を評価する』に近いでしょうか. ↩︎

  7. 実際には,関数規則のカリー化といった,fixの利用に関わる様々な考え方があるのですが,ここでは省略します. ↩︎

  8. fix等による関数規則の名前付けは必要となります. ↩︎

  9. キーが見つからなかった時は,空リスト[]や偽の値であるFalseではなく,-1を返すようにしています.これは,見つかった時に返す値と同じ型である数値とすることで,型の指定を省略するためです. ↩︎

  10. どちらかというと,記号処理や(その名の通り)リスト処理を中心に据えたパラダイムと言えます. ↩︎

  11. これは,Common Lisp等の伝統的な仕組みをもつLISP系言語では,同じ変数で,数値や値としての記号を格納する領域と,関数規則を格納する領域のふたつをもつよう,仕様や言語処理系が設計されているためです. ↩︎

  12. 文字列としてダブルクォートで囲まれた場合を除きます. ↩︎

  13. Common Lispの多くの構文・組込関数を利用できるようにするライブラリGNU Emacs Common Lisp Emulationが組み込まれており,Common Lispでの名称の冒頭にcl-を付ければ利用できるようになっています. ↩︎

  14. fpLISPを含む他のLISP系言語では,レキシカルスコープと呼ばれる『字義通りの』名前参照の仕組みを採用していますが,Emacs Lispでは,ダイナミックスコープと呼ばれる『実行時に参照範囲が決まる』名前参照の仕組みが標準です. ↩︎