Lambdaカクテル

京都在住Webエンジニアの日記です

Common Lispで変数名に使える文字が自由すぎる

おおざっぱなタイトルすぎる。

Common LispやSchemeといったLisp族はきわめて構文の構造が簡素である,といったことはわざわざ説明の必要もないでしょう。リストとして式のASTを表現するわけです。

(defun foo (bar) (+ foo bar)) ; あらゆる構文がリスト

構造が簡素ということは,それを表現するのに必要な,言い換えると予約しなければならない文字があまり無い,ということでもあるわけです。

事実,Common Lispでシンボルとして使えない文字を探してみると,他言語よりは少ないのです。ちょっと探してみましょう。

使えなそうな文字

()

言わずもがな,Lispはカッコでリストを表現するのでカッコは使えない。

'

リストをコードではなくデータとして読ませたいときにはクオートquoteという表記でそれを指示する。この記号にはアポストロフィが割り当てられており,シンボル名には使えない。バックティックもだいたい同じ理由で使えない。

空白

そりゃそうだ。

#

ハッシュサインはCommon Lispの読取器に指示を出すために,しばしばクオートと隣接して'#のように使われる。'#()と表現するとベクタである。'#2A((1 2 3) (4 5 6))と書くと2次元配列になる。

ちなみに関数シンボルは#'fooといったように書く。まぎらわしい。

:

コロンはパッケージ名とシンボルとの区切りとして使われる。例えばCL-USER:FOOCL-USERパッケージのFOOというシンボルを指し示す。Perlのパッケージは::というふうに2文字重ねる必要があるのだが,2文字も要らんやろという感情がある。

ちなみにCommon Lispではコロンを重ねて::と書くと,export操作をしていないプライベートなシンボルにアクセスできる。カプセル化を破る,注意が必要な操作だということが見た目で分かるようになっている。

"

他の言語がそうであるように,ダブルクオートは文字列リテラルのために予約されている。

.

ドットはドット対リテラルのために予約されている。

……とおもいきや

* (defparameter foo.bar 123)
FOO.BAR
* (+ foo.bar foo.bar)
246

クオートの外では使えるようだ。

\

バックスラッシュは文字を表現するときに使うことがある。

* (defparameter foo\bar 666)
|FOObAR|

なんか不穏な状態になった。怖いからやめておこう。

いろんな文字で変数定義してみる

さて,前述した以外の文字は(たぶん)だいたいなんでも変数名として,より正確にはシンボル名として使うことができます。実際にやってみましょう。

アスタリスクはSBCL(処理系)のプロンプトです。

@

まずはPerlライクにリストを@で表現してみる。

* (defparameter @array '(1 2 3))
@ARRAY
* (mapcar #'* @array @array)
(1 4 9)

普通に使える。とはいえリストはあまりによく使うのでわざわざ@で修飾するまでもないかもしれない。

ちなみに準クオートquasiquoteでリストを展開するときに@を使うのでまぎらわしいかもしれない。

* (defparameter foo '(1 2 3))
FOO
* `(a b ,@foo)
(A B 1 2 3)

%

次にハッシュを%で表現してみる。

* (defparameter %hash (make-hash-table :test #'equal))
%HASH
* (setf (gethash "foo" %hash) 123)
123
* (gethash "foo" %hash)
123
T

普通に使えたけど,Common Lispでハッシュから値を取り出すためにはgethash関数という専用の関数を使うわけです。このため渡されている変数がハッシュなのは自明といえば自明。 ところでgethashの引数の順序,皆さん納得できてますか?キーが最初でハッシュが最後ですよ???おかしくないですか?????OOPで培われた常識が崩れていきます。

(gethash
  key ; まずキーを指定して...
  hash ; 次にハッシュテーブルを渡す
)

ちなみに関数内でループ用の関数を定義するときにプレフィックスとして%がよく使われている気がします。

不等号

クラスを定義してみましょう。Common Lispではクラス名を不等号で修飾する習慣があった気がします。

* (defclass <foo> () ((x :initarg :x :accessor x) (y :initarg :y :accessor y)))
#<STANDARD-CLASS COMMON-LISP-USER::<FOO>>
* (defparameter fooinstance (make-instance '<foo> :x 12 :y 34))
FOOINSTANCE
* (x fooinstance)
12

普通に使えますね。 defclassをソラで書けるようになったら大人です。

スラッシュ

スラッシュを使ってみます。Perlをはじめとする言語では正規表現リテラルに使われていますね。正規表現を表現する変数名として使ってみます。

* (ql:quickload :cl-ppcre)
To load "cl-ppcre":
  Load 1 ASDF system:
    cl-ppcre
; Loading "cl-ppcre"
..
(:CL-PPCRE)
* (defparameter /regex/ (ppcre:parse-string "regex"))
/REGEX/
* (ppcre:scan /regex/ "abcregexdef")
3
8
#()
#()

あんまりうまみを感じない。

[]

配列用の構文がないので角カッコも自由に使えるけれどあまり使い道が思い付かない。1次元配列であるベクタで使ってみましょうか。

* (defparameter [vec] #(1 2 3))
[VEC]
* (defun dotproduct (v1 v2) (reduce #'+ (map 'vector #'* v1 v2)))
DOTPRODUCT
* (defparameter [vec].[vec] (dotproduct [vec] [vec]))
[VEC].[VEC]
* [vec].[vec]
14

内積が定義できた。

ブレース{}も使えそうだけど用途がまったく思い付かない。

はてなでわっしょい

Common Lispでは述語(tかnilかを返す関数)には末尾にp(predicateの略)をつける慣例があるが,Schemeでは?を末尾に付ける。個人的にはSchemeのスタイルが好きなのだが。

* (defun string? (x) (typecase x ((string) t)))
STRING?
* (string? 123)
NIL
* (string? "foo")
T

->

Schemeでは何かから何かへと変換するような関数は->でつなぐ。Common Lispにそういう慣例はなさそうだ。

* (defun string->integer (s) (parse-integer s))
STRING->INTEGER
* (string->integer "666")
666
3

命名に関してはSchemeのほうがより洗練されているように感じる。

感想

命名規則的な感じでシンボルの中身に対応する記号を修飾するようにすると,もともとLispは無味乾燥として見えがちなので,コードが読みやすくなるかもしれない。

とはいえハンガリアン記法みたいになっても嫌な気がする。

ちなみにGoogleのCommon Lispスタイルガイドというのがあって,一般的な慣例は網羅されているのでこれに従うとよいだろう。

みなさまもLispで見掛けた面白い命名があったら教えてください。

オチ

実はパイプ(バーチカルバー)でくくると,その中ではだいたい何でも使えるようになります。

* (defparameter |foo bar buzz '() ::::| 1)
|foo bar buzz '() ::::|

めでたし

貴様!この記事の筆者をフォローしてください!(しなさい)