データ型
| Type | Example | Description |
|---|---|---|
| Integer | 42, -5, 1,000, #xff, #o777, #b1010 | 64ビット符号付き整数。オーバーフロー時には自動的に多倍長整数へ昇格します(インタプリタおよびJVM)。WASMでは31ビット符号付き整数です。#x/#o/#b は16進/8進/2進リテラルとして読み込まれます |
| Ratio | 1/3, -2/5 | 正確な有理数(Common Lispのratio)。常に正規化され、3つのバックエンドすべてでサポートされます |
| Double | 3.14, -0.5, 3,000.50, 1d0, 6.02e23 | 64ビット浮動小数点数 |
| String | "hello" | 文字列リテラル |
| Character | #\a, #\Space, #\Newline | 文字リテラル(#\ の後ろに字形または標準名: Space, Newline, Tab, Return, Page, Backspace, Nul, Rubout)。WASMバックエンドは文字列をバイト単位でインデックスするため、非ASCII文字は対象外です |
| Symbol | x, foo | 識別子 |
| Keyword | :foo, :bar | : で始まる自己評価シンボル |
| Nil | nil | 偽 / 空リスト |
| T | t | 真 |
| Pi | pi | 定数 π。double値 3.141592653589793 として読み込まれます |
| Fixnum 範囲 | most-positive-fixnum、most-negative-fixnum | pi と同様に自己評価する整数として読み込まれます。値はバックエンド依存です (WASM の fixnum はアンボックスの 31 ビット参照、インタープリターと JVM バックエンドは 64 ビット long) |
| Cons | (1 2 3), (a . 1) | consセルで構築された連結リスト。(a . b) は単一セルを表すドット対記法 |
| Function | #'car, (lambda (x) x) | #'/function/lambda で得られる関数オブジェクト |
| Array | #(1 2 3), #2A((1 2) (3 4)) | 任意の階数の固定サイズ配列(階数1はベクタ)。#(...) と #nA(...) は自己評価される配列リテラルです |
| Hash table | (make-hash-table) | 構造的(equal)なキーを持つ可変のキー/値テーブル |
数値リテラルでは、整数部の桁の間に区切り文字として , を使用できます。たとえば
1,000 は 1000 として読み込まれ、(+ 1,000 100) は 1100
に評価されます。カンマが区切り文字として扱われるのは2つの数字の間にある場合のみで、パース前に除去され、3つのバックエンドすべてに適用されます。これは
, がunquote文字であるCommon Lisp(ここではサポートされません)とは異なります。
浮動小数点リテラルにはCommon Lispの指数マーカーを付けられます。仮数部の後ろに
e, s, f, d, l のいずれか(大文字小文字を区別しません)、省略可能な符号、そして指数を続けます。例:
1d0, 1e0, 1.5d3(1500.0)、-2e-3, 6.02e23。これは3つのバックエンドすべてで機能します(リーダレベルの機能です)。Common
Lispとは異なり、rontolispは浮動小数点型を1種類しか持たないため、すべてのマーカーは同じ
64ビットdoubleとして読み込まれます -- single/short/long-floatの区別(1d0 対 1e0 対
1f0)は保持されず、*read-default-float-format* もありません。指数の数字が後続しないマーカーは浮動小数点ではありません。1d
と 1d0x は(1+ と同様に)数値ではなくシンボルとして読み込まれます。
インタプリタおよびJVMコンパイラでは、整数演算が暗黙のうちにラップすることはありません。long
演算(+, -, *, /, 1+, 1-, abs, ...)がオーバーフローする場合、結果は自動的に任意精度の多倍長整数へ昇格し、long
より大きい整数リテラルは多倍長整数として読み込まれます。long
に再び収まる多倍長整数の結果は再度降格されるため、値は単一の正規表現を保ちます。たとえば
(defun fact (n) (if (= n 0) 1 (* n (fact (- n 1))))) の場合、(fact 32) は正確な値
263130836933693530167218012160000000 を返します。WASMコンパイラはこれをサポートしません。整数は31ビット(i31ref)に制限され、オーバーフローはラップします。
3つのバックエンドすべてがCommon Lispのratio(正確な有理数)をサポートします。1/3
はratioリテラルとして読み込まれ、割り切れない整数除算は切り捨てる代わりにratioを返します:
> 1/3
1/3
> (/ 1 2)
1/2
> (+ 1/2 1/3)
5/6
> (/ 1 2.0)
0.5
> (float 1/2)
0.5
ratioの結果は常に正規化されます。gcdで約分され符号は分子に付き(2/4 は 1/2
として読み込まれます)、分母が1に約分される場合は整数へ降格されます((/ 10 2) は 5、(+ 1/2 1/2)
は 1)。算術、比較(= < > <= >=)、eq/eql、abs/min/max/1+/1-/signum、述語(numberp,
rationalp, zerop, plusp, minusp)、truncate/floor/ceiling/round、整数の指数を持つ
expt((expt 2 -1) は 1/2)、numerator/denominator
はすべてratioを扱います。浮動小数点が混ざると浮動小数点への伝播に切り替わります。単項の (/ x)
は逆数です((/ 2) は 1/2)。
バックエンドごとに、各成分は整数表現に従います。インタプリタおよびJVMコンパイラは多倍長整数を使用し(巨大な分子/分母のratioも正確なまま)、WASMコンパイラは他のすべての整数演算と同様に、オーバーフロー昇格なしで31ビット
i31 の範囲に保ちます。コンパイルされた read/load のために発行されるランタイムリーダはratioリテラルをパースしません(実行時に読まれる
1/3 トークンはシンボルになります)。また mod、evenp/oddp、gcd/lcm、isqrt
は整数専用のままです。
コメント・フィーチャー条件・*features*
; の行コメントに加えて、リーダは Common Lisp の #| ... |#
ブロックコメント(標準どおりネスト可)と #+/#- フィーチャー条件をサポートします。#+expr form はフィーチャー式が成立するときだけ
form を残し、#-expr form は成立しないときだけ残します。フィーチャー式はフィーチャー名、または
(and ...)/(or ...)/(not ...) の組み合わせです(裸のシンボルでもキーワードでもよく、大文字小文字を区別しません)。アクティブなフィーチャーはすべてのバックエンドで
:rontolisp、加えてバックエンドを識別するフィーチャー — :rontolisp-interpreter、
:rontolisp-jvm、:rontolisp-wasm — が1つで、1つのソースファイルでバックエンドごとのコードを選択できます。変数
*features* はアクティブなフィーチャーのリストとして読まれます(pi
と同様に read 時に固定される quote されたキーワードのリストで、代入はできません)。
注意点:
- 読み取りはフロントエンドで一度だけ行われます。インタプリタは
:rontolisp-interpreterで読み、.class/.wasmへのコンパイルは:rontolisp-jvm/:rontolisp-wasmで読むため、コンパイル済みプログラムのフィーチャーセットはコンパイル時に固定されます。コンパイル時のload/require/asdf:load-systemインクルードで取り込まれるファイルも、同じターゲットフィーチャーで読まれます。 - 不成立の
#+/#-ガードでスキップされるフォームは、パースされずに生の文字レベルでスキップされるため、rontolisp がサポートしない構文を使っていても構いません(それがガードの目的です)。 #.の read 時評価はサポート されず、明確な read エラーになります。唯一の例外は.asdファイルで、そこでは#.フォームは警告付きでスキップされます(システムガイドを参照)。- コンパイル済みプログラムのランタイムリーダ(
read、read-from-string、実行時のload)は、backquote と同様に、ブロックコメントとフィーチャー条件を知りません — コンパイル済み read/load の制限を参照してください。 :common-lispは意図的に*features*に 含まれません: rontolisp はサブセットであり、準拠実装ではないからです。
ドット対・連想リスト・属性リスト
リーダはCommon Lispのドット対記法をサポートします。(a . b) は car が a、cdr が
b の単一のconsセルを表し、(a b . c) は最後の cdr が nil ではなく c
になるリストです。連想リスト(alist)のリテラルはこの記法で書きます:
ドット末尾はバッククォートテンプレートでも機能し(`(a . ,x) は cons
の連鎖に展開されます)、コンパイルされたプログラムのランタイムリーダも同じ記法をパースするため、"(a . 1)"
の read/read-from-string はすべてのバックエンドで同一に動作します。リストの外に単独で現れる
. はCommon Lispと同様に読み取りエラーです。またバッククォートテンプレート内では
,@ とドット末尾を組み合わせることはできません。呼び出し位置のドット末尾((+ 1 . 2)
など)は3つのバックエンドすべてでエラーになります -- ドット対はデータとしてのみ意味を持ちます。
連想リスト関数群 -- assoc、assoc-if、rassoc、acons、pairlis、copy-alist
-- は3つのバックエンドすべてで機能します。assoc と rassoc は既定では eql
で比較し、member と同様に省略可能な :test/:key キーワード(:test は関数指定子。
文字列キーには #'equal など。:key は比較の前に各ペアの car/cdr へ適用される
セレクタ)を受け付けます:
属性リスト(plist) -- (:a 1 :b 2) のようにインジケータと値が交互に並ぶ平坦なリスト
-- は、キーワードを使うalistの親戚です。getf はインジケータに対応する値を読み取り(2引数のみ:
&optional default はありません)、remf マクロは変数などの setf
プレースに保持されたplistからインジケータと値の組を取り除きます。ラムダリストの &key
パラメータも同じ形から解析されます。(setf (getf ...)) はサポートされるプレースではなく、シンボルの属性リスト(get/symbol-plist)もありません。エントリの追加や更新にはリストを作り直します(list*
で先頭に追加するなど):
配列
make-array、aref、(setf (aref ...)) は3つのバックエンドすべてで機能します。
階数1以上の任意の階数の配列がサポートされます。dimensions引数は整数(階数1)または空でない整数のリストで、:initial-element
はすべてのセルを設定します(デフォルトはnil)。要素は行優先で格納されO(1)でアクセスでき(階数に依存しないフラットなアクセスは
row-major-aref /
array-row-major-index)、配列は同一性(eq)で比較されるため、異なる2つの配列が
equal になることはありません。length
はベクタ(階数1の配列)の要素数を返します。多次元配列はシーケンスではないため、length
はエラーを通知します。ハッシュテーブル演算子とは異なり、配列演算子は第一級の関数値として公開されないため、#'aref
と #'make-array は利用できません(直接呼び出してください)。
ベクタは vector で構築し、svref
で読み取ることもできます。配列の形状は
array-dimensions /
array-rank /
array-total-size で調べられ、
coerce はリスト・ベクタ・文字列を相互に変換します。
配列の上で numpy スタイルのベクトル・行列演算を行うには
linalg パッケージを参照してください。
ネストしたループでインデックス参照する2次元配列:
#(...) リーダ構文は、要素がデータとして(評価されずに)読み込まれる自己評価される階数1のベクタリテラルを表します。例:
#(1 2 3) や #(a "b")。階数nの配列は、内容を深さnのネストしたリストとして #nA((...) ...)
と書きます(行列は #2A、階数3の配列は #3A、...)。同じ深さのリストはすべて同じ長さでなければならず、
不揃いな内容は読み込みエラーになります。配列はすべてのバックエンドで同じ可読構文で印字され、prin1
は文字列要素を引用符で囲み、princ は囲みません:
パックド浮動小数点配列(#d / #f)
#d(...) と #f(...) はパックド浮動小数点配列を表します。これは要素をアン
ボックスで格納する浮動小数点型の配列です。#d(...) は double-float(f64)、
#f(...) は single-float(f32 -- メモリは半分、SIMD レーン数は 2 倍)です。
#(...) と同じ記法で読み込まれますが、各要素は配列の浮動小数点型に強制変換される
ため、#d(1 2 3) と #d(1.0 2.0 3.0) は同じベクトルであり、
(array-element-type #d(1.0)) は double-float(#f なら single-float)になり
ます。高階のリテラルはネストしたリストで書き(#d((1.0 2.0) (3.0 4.0)) は行列)、
実行時には (make-array n :element-type 'double-float)(または 'single-float)
で構築できます。
スカラは double のままです。要素を読むと double に拡張され(single-float 要素は
f32 -> f64 に拡張)、格納するときは配列の幅に丸められます(single-float 配列では
f64 -> f32)。非実数を格納すると型エラーです(一般配列は任意の値を保持します)。
それ以外のすべての操作 -- aref・(setf (aref ...))・length・row-major-aref・
array-rank・array-dimensions・coerce -- は同じ数値の一般配列と同じように機能し
ますが、印字だけは独自の #d(...) / #f(...) リーダ構文を用いるため、その印字結果を
読み戻すと(アンボックス表現を保ったまま)同じ幅のパックド配列になり、一般配列に劣化
しません。数値カーネルが用いる、アンボックスで浮動小数点に特化した表現に過ぎないため、
フィルポインタ・可変長(adjustable)・ずらし配列(displaced)は利用できません(それ
らには一般配列が必要です)。double-float の幅が既定で、linalg が生成するのもこの
幅です。パックド配列上の高速なベクトルカーネル(および任意のハードウェアアクセラレー
ション)については vec パッケージを参照してくだ
さい。
ハッシュテーブル
make-hash-table、gethash、(setf (gethash ...))、remhash、clrhash、hash-table-count、hash-table-p、maphash
は3つのバックエンドすべてで機能します。キーは構造的に(equal のように)比較されます。(list r c)
のようなリストキーは等しいリストにマッチし、数値・シンボル・文字・文字列は値でマッチします。:test
は馴染みのために受け付けられますがこの挙動を変えません -- eql
テーブルでも構造的に等しい集約キーにマッチします。反復順序(maphash)はバックエンド間で保証されないため、ポータブルなコードはこれに依存すべきではありません。これらは3つのバックエンドすべてで第一級の関数値としても使用できます(#'gethash、#'remhash、#'clrhash、#'hash-table-count、#'hash-table-p、#'maphash、および無引数形式の
#'make-hash-table) -- 固定アリティのラッパー経由で渡されるため、gethash の省略可能なデフォルトや
make-hash-table
のキーワード引数は関数値経由では利用できません。典型的な使い方 -- placeに対する incf
でのカウント: