はじめに
拙著:マンガで分かる数学入門…という名のマンガで分かる圏論入門
では、様々な方にお世話になりました。
ありがとうございます。
この投稿は、自分で思いついたシンタックス(構文)をメモしたいという側面もあります。
読みやすく書きやすいプログラミング言語を作ることは可能か?というニーズに答えるために、まずは設計だけを行いたいと思います。
なお、ツッコミなどのコメントは大歓迎です。
プログラミング言語の設計
プログラミング言語の設計といっても、何をしたら良いのか分からないという方を対象に、ものの考え方とその理解についてから委細を解説していく試みです。
今回は、代数学に由来する関数型言語と、集合論に由来するオブジェクト指向の双方から文脈を考察し構文を構築していく方向で、設計をしてみようと思います。
本言語のシンタックスについて
この言語は、関数型で高階多相型をなるべく可能とするための構成を模索します。(無謀だと言われそうですが…)
評価戦略
以下のような評価戦略を考えました、
- 意味のないコードは必ず実行されないという徹底した戦略を取ります。
つまり遅延評価です。 - 関数の最終行を必ずreturnします。これにより、幾つかの構文を可能とします。
- 一つのファイルは必ずスコープを持つため、ファイルの実行が完了したらそのファイルにまつわるオブジェクトは原則破棄します。
- 再帰をループとし、最新の結果をメモ化する事で2つ前のスタックを強制的に開放できます。
- 静的リンクよりも動的リンクを適用します。
- 全ての演算子は関数であり、全ては随伴関手に従います。
- 全ての記号を予約語とし、アルファベットを予約語から開放します。
つまり必要かつ自由な名前を付け放題です。 - この言語での名前の衝突は、実装の衝突と定義されます。同名の関数ならば実装はさほど変わらない事を目指します。
つまり、名前の衝突=定義の衝突なので、エンジニア同士できちんと話し合って仕様を決め直しましょう!
設計されたもの…
ここからは実際のソースコードにあたるものを貼り付けます。
これがコンパイルできることを目指して行きたいと思います!
`文字列は、backQuort`
`意味をなさないリテラルだけの開始行や中間行は実行されない。よって、上の行も実行されない。`
`リテラルだけの行であっても、それが関数の最終行であれば、それを返値とする。`
`よって、コメントは文字列で書く。`
`改行は「評価する」という後置演算子。`
`改行が評価を表さないようにしたい場合、改行文字を使う。`
`行頭indentはブロックの形成となる。`
`この言語の通常の評価戦略は、遅延評価である。`
`":"は英文では「即ち」なので代入演算子とし、=は飽くまで比較演算子である。`
`下を見ただけでも、この言語は名前が型となるのは自明であろう。`
x: -353.15134
y: 4001.35364502
Hello: `Hello`
World: `World`
`trueは全ての有効値、falseは0か長さ0のリスト`
yep: 1
nop: 0
`ブロックになっていない代入演算子は右単位元である。`
unit: none: []
`上述は、以下の構文と同じ意味`
none: []
unit: []
`この言語ではカッコ、インデントの双方はブロックを表し、ローカルスコープを持ち、優先に処理されるものと認識される。`
`つまり、名前のスコープはブロックで決まる`
`エラー回避のため、以下をコメントアウトする`
` y: 1`
` n: 0`
`上述はブロックを抜けると削除されるため、y wasn't defined というエラーと同時に、none(unit)を返す。`
`[y = 1] = 0`
`ただし、以下であればアクセスできることになる。`
[
`y`: 1
`n`: 0
] `y`
`ラムダ項の定義方法`
`ラムダ項の基本は、?を使う。`
`代入を伴わない関数は、カッコで囲む必要がある。なぜなら、カッコの最初は、スタックの生成を意味するから。`
[x y ? x ^ 2 + 2 * x * y + y ^ 2]
`上述は因数分解によって、この式は以下と等価。`
`カッコはあくまでブロック(スタック)の定義開始として捉え直すことで、式の優先順位を表現できている。`
[x y ? (x + y) ^ 2]
`ポイントフリーと関数合成を使って、更に記述を簡約出来る`
`ポイントフリーな関数の記述は、直接的な演算子の記述であるため、型が自明である。`
`関数になった段階で演算子としての優先順位は失われ、関数合成は左単位元となる。`
[+] [^ 2]
`もし[^2]を優先する表記をポイントフリーで記述するなら…`
[^ 2] [+]
`となる。`
`比較演算はショートハンドがある。`
`[x = y] & [y = z]`
x = y = z
`[1 <= x] & [x <= 9]`
1 <= x <= 9
`関数適用や合成をさせたくない場合は、関数のすぐ後ろに、,を打ち、直積とすることで対応する。`
[+ 2] 2 = 4
[+ 2], 2 = [+ 2], 2
`ラムダ項は定数代入や定数関数も記述出来る`
iterate3 : 1 ? 2 ? 3
`後ろに空白があることで評価される。`
iterate3 = 1
iterate3 = 2
iterate3 = 3
iterate3 = []
iterate3 = []
`単項式のポイントフリーな記述は、右、左の順に引数をとる。`
`関数の一番最後の引数は、関数の前に書いても良い。`
`:の後ろにある場合、ポイントフリーな記述が単項式であるなら、[]を省略可能。`
result2 : - 1 3
add : +
exp : ^
id : x ? x
`関数評価の優先順位はラムダ定義よりも低い。`
`関数合成は左単位元だが、通常の関数実行を記述すれば右単位元として記述可能。`
`本言語でのカッコの種類による違いは無い。`
`以下は、headを返す関数として機能する。`
[x ? x] 1 2 3 = 1
[x ? x] 1, 2, 3 = 1
[1 2 3] '0 = 1
[1, 2, 3] '0 = 1
`以下はtailを返す関数として機能する。`
[_ ~y ? y~] 1 2 3 = 2 3
[_ ~y ? y~] 1, 2, 3 = 2 3
[1 2 3] '1~ = 2 3
[1, 2, 3] '1~ = 2 3
`この言語は、どうしても型を記述したい人のために"で名前を囲むことで、型付けや型の読み出しは出来る(非推奨)`
`なぜ、構文が正しければ型が自動で正しくなるかは、以下の型付けが自明`
`ある関数が適用された型が、そのまま関数が適用された型として表現されるため、実装と同じになる。`
`当然、関数が実行された型は、実行後の型に自動で型キャストされることになるが、`
`これは遅延評価であるため、型の計算が後で成されることと関係している。`
`結果、型安全をコンパイラやインタプリタが保証しなくとも良くなる。`
`高階関数に渡す引数は直積によって評価するかしないかを分けて書く必要がある。`
`match_caseは、ブロック構文を使い、以下のように記述出来る。`
`ブロックがガード記法となる。`
`ブロック化された代入演算もこれが正体`
`ブロックそのものを代入するかカッコでくくって即時生成すれば、ただの関数と同じ。`
`ラムダを無名のまま使用する場合、カッコで括られていないといけない。`
[x ?
x = 0 : 0
x > 0 : `more`
x < 0 : `less`
`other_wise`
] 3
`上述はプリプロセスで、以下に置き換えられる。`
[
x ?
x = 0 & [_ ? 0] ;
x > 0 & [_ ? `more`] ;
x < 0 & [_ ? `less`] ;
[_ ? `other_wise`]
] 3
`コンストラクタのような記述。`
`動的なキー名を割り当てることも可能で、その場合は変数や引数に代入された値を強制で取り出すには~を使う。`
Person : name age etc x ?
name
age
~etc : x
john : Person `john` 18 `Like` `Sushi`
john `name`
`Quoteは以後のidentを文字列としてそのまま利用する。`
john 'name = john `name`
`person インスタンスのコピーを展開演算子で行い、値を上書きして作る`
person : Person [] [] [] []
mary :
~person
'name : `mary`
'age : 16
charie :
~person
'name : `charie`
'age : 24
`ここより以下はショートハンド記法の方を使う。`
[
0 : `zero`
> 0 : `more`
< 0 : `less`
`other`
] 3
`3項演算子のような振る舞いも、以下のように定義可能。`
[
1 : `yep`
0 : `nop`
] 1
`これも、match_caseと同等であり、この記述により if then を再現可能`
`otherwiseの記述がない場合で、otherwiseにマッチした場合は、渡された値をそのまま返す。`
[> 3 : [+ 3]] 3 4 = 4
`データクラスっぽい記述もmatch_caseのショートハンド。`
Item :
name
equip
use
effect
medicalWeed :
Item
`medicalWeed`
[]
['medicalWeed], [- 1]
['HP], [+ 20]
lightningStaff :
Item
`lightningStaff`
['Atk], [+ 8]
['ThunderBolt]
['HP], [- 40]
`いわゆるlet ~ inも以下のように記述する。`
`実はモナディックな記述と同じになる。`
myValue :
3
[+ 4]
[* 2]
`リストの定義は原則comma区切り。`
`","は英文では「強い区切り」、「直積」を表す。`
`" "は「直和」に使われる。`
`スカラー型の余積な場合、commaを省略出来る。`
`ラムダの評価が、他の式や項より優先順位が低いのもよく分かる。`
`リストがイテレータとして機能する様も、頭の関数の省略がされてない形を見るとよく分かる。`
`実装が型であることを、見事に再現できている。`
`同じように、","と" "が随伴関手定理による随伴であることも現れている。`
`これは引数を定数とする関数の定義とも一致するが、引数の定数関数はイテレータの扱いとなる。`
`イテレータに~演算子をつけることで、イテレーㇳされる`
myPairs: 1 2 3 4 5
myPairs0: [,] 1 2 3 4 5
myPairs1: 1, 2, 3, 4, 5
myPairs2: 1 ? 2 ? 3 ? 4 ? 5
myPairs0 = myPairs = myPairs1 = [,] ~myPairs2
`よって、高階の記述も自然に行える、これは概念上はカンマを何かの関数に差し替えて畳み込むだけである。`
`よって畳み込み(foldL)は、2引数関数にリストを渡すだけとなり、これはLISPと一致する。`
`本言語の初級者や実装を優先したい人のために、これを普通の単語で理解しやすいような記述にするための`
`ライブラリの提供は考えている。`
[[,],] myPairs = [1], [2], [3], [4], [5]
[?] myPairs = 1 ? 2 ? 3 ? 4 ? 5
[+] myPairs = 15
`mapは、この様な記述で可能で有るが、`
[* 2,] myPairs = 2 4 6 8 10
`これは直和(配列の合成)`
r: [1 2] [3 4]
`これは直積(2次元配列)`
s: [1 2],[3 4]
`辞書型に見える表現は、はmatch_caseを行う無名関数と同じでカッコの種類は問わず、インデントでも定義可能。`
`ただし、開カッコと閉カッコは必ず対になる必要がある。`
myGreet:
greet:
hello: `hello,`
welcome: `welcome,`
world: ` world`
`上述、myGreetは、プリプロセスで以下に置換される。`
myGreet: ~a ?
a '0 = `greet` &
a '1 = `hello` & `hello,` ;
a '1 = `welcome` & `welcome,` ;
a '0 = `world` & ` world`;
[]
`よって、アクセスはidentか文字列、またはnaturalかhexを引数にすることになる。`
myGreet 'greet 'hello = `hello,`
myPairs '0 = 1
myPairs '[1 ~ 3] = 2 3 4
`ブロックスコープを使うことで、ブロックスコープ内にgetを直接記述できる。`
`このブロック構文も、もちろん演算終了時に返り値がある。`
`これはブロックスコープでの結果の直和が可能であれば直和を返す。`
myGreet
'greet
'welcome
'world
= `welcome, world`
`値のmodifyが可能`
myGreet
'greet
'welcome : `welcome to our `
'world : `metaverse!`
`プリミティブな型のみを推論することで、実際の型の問題が解決できるような設計となる。`
`与えられた名前は全て定義であり、それが自明な型でもあるため、名前が型である。`
`~演算子は、対象のスコープ上に辞書型を模倣するmatch_caseのみを展開できる。`
`前置であれば、他の項より優先され、後置であれば他の項を優先する。`
~[
y: 1
n: 0
]
y = 1
`@前置演算子はimport。ファイル名からの指定も可能で、path指定も可`
`match_caseが辞書型とみなせる場合、~演算子で一つ上のスコープへ展開できるため、~@を使うことで、ファイル内で使えるインポートも可能。`
@io
say Hello World
`ファイルスコープ上のインポートではないため、下の行は、Say is not definedエラーを返す。`
say Hello World
`ファイルスコープへ展開して使う。`
~@Funnctor
~@Monoid
~@io
say[1 2 3 4 5 [* 4,] [+]]
`\の後の任意の1文字は、必ず文字型の値1文字分とする。`
`文字の連結の場合、\の後は1文字と決まっているため、identや文字列が来れば、素直に文字列結合とみなす。`
`スカラ値のリスト化は、通常はspaceかcommaを使うが、文字リテラルと文字列リテラルの場合は、`
`直結した記述で連結を表わせる場合があるので、そのパターン記述しておく。`
`文字列はあくまで文字のリストである。`
`ただし、実用的な文字値の記載は、改行の挿入や`
M: \M
My: M \y\ \D\o\m\e\s\t\i\c
My = M \y \ \D \o \m \e \s \t \i \c = `My Domestic`
`Hello ` `World!` = `Hello World!`
`Hello` \ `World!` = `Hello World!`
\H `ello` \ `World` \! = `Hello World!`
`Hello ` My `World!` = `Hello My Domestic World!`
Hello \ My \ `World` \! = `Hello My Domestic World!`
`Hello` \!\ My World \! = `Hello! My Domestic World!`
`\ バックスラッシュを含む文字列は定義できる。エスケープシーケンスではないので要注意`
`back_quortが含まれる文字列の定義は、back_quortを文字として扱い、文字列結合(余積)で扱う`
`Sign\s ` \` `define a string literal`
`改行を含む文字列を表現したい場合は、ブロックのような書き方となる。`
`理由は、" " が、「リストの結合、及び関数合成、関数適用」を意味するため。`
`改行を含めたい場合。`
HWinEnter :
`Hello` \
`World!`
`"#"はexport前置演算子になる。`
`通常の関数は必ず最終行の値を返し、match_caseは、指定値に対応する値や関数を返す`
`ファイル全体は一つの関数でもあるが、export つまり#で示されたもの以外は自身のスコープを出ることはない。`
#myDict : name value ? ~name : value
#gets : name ? myDict '~name
`論理演算子は短絡評価される。`
0 & `me` = 0
1 & `me` = `me`
0 | `me` = `me`
1 | `me` = 1
0 ; `me` = `me`
1 ; `me` = 0
`以下の行はコンパイラのデバッグ用`
#`It:`\ `365` `is number of date at 1 year`
\ 4 + 5
長くなりましたが、実装はchatGPTと一緒にやろうと思います
実装をclaude3.5Sonnetと頑張っています😅