derarion.dev

私が考える初学者フレンドリーなプログラミング言語 Momonga の仕様

ここ数ヶ月、言語処理系の学習として、空いた時間で Momonga という自作言語を Rust で実装してきました。ネーミングに特に意味はないですが、語感と、日本製の動物ということでモモンガを選びました。
完全に学習目的ですが、テーマがあった方が仕様に一貫性がでるので初学者が一番最初にやるプログラミング言語と決め、言語仕様を自分で考えて実装しました。

Momonga は WebAssembly コンパイルしてブラウザの Playgroud で公開予定ですが、今回は先に Momonga の言語仕様をご紹介したいと思います。

Momonga の3大コンセプト

想定するユーザーは、プログラミングを初めてやる初学者です。
いままで一度もプログラムを書いたことがなく、「プログラミング」がどういうものかを知りたい、というようなレベル感とします。

一般的かつ基本的な構文で、何らかのアルゴリズムを記述することを学習のゴールとします。Momonga で1週間ほど学習した後、 Python や C, JavaScript, Ruby などのメジャーな言語に移行してもらうような位置づけです。

これを実現するべく、Momonga には下記の3大コンセプトがあります。

  1. 基本的で一般的な構文のみをもつ
  2. 必要最低限の概念のみをもつ
  3. 環境構築不要で、オンライン実行環境がある

それでは1つずつ説明していきます。

1. 基本的で一般的な構文のみをもつ

構文や演算子等がペライチに収まるくらいのボリュームを意識しました。というか、そんなにいろんな構文を作ることは現実的じゃないですが・・・

また、メジャーな言語への踏み台ですので、メジャーなスクリプト言語、というかほぼ JavaScript のようなものになりました。JavaScript の構文は 様々な言語の源流とも言える C に構文的に通ずるところがあり、オーソドックスだと思います。初学者に絶大な人気を誇る Python ですが、やはりインデントでブロックを表現するというのはプログラミング道においては特殊かと思います。

ただし、初学者はそもそもタイピングに慣れてないことも多く、タイポで syntax error になることが多いので、タイプ量の少ない構文も意識しました。(functionfuncにするなど)

構文を厳格に説明するなら、実装に採用した パーサジェネレーター Pest のオンラインエディタでMomonga の PEGをご覧頂けば、言語処理系の知見がある方は分かるのかもしれませんが、多くの方にイメージが湧くようにコードスニペットで説明していきます。

演算

Momonga
((2 + 3) * 5 - 7 / 11) % 13 == 12; // true

こちらは Integer 型 の演算です。
Float 型がまだ開発中なのですが、それでも Momonga では暗黙のキャストは行わない(後述)ので、演算結果としてtrueになります。
式文(Expresion statement)となります。

その他、比較演算子(==, !=, <, >など)、論理演算子(&&, ||, !)も基本的なものをサポートしています。

変数の宣言

Momonga
var x = 42;

唯一の変数宣言子であるvarを用います。これは、変数の宣言子が複数あることは初学者にとって混乱を招くという考えからです。

逆に、省略もできません。
Python や strict モードでない JavaScript では、(初期化を伴えば)変数宣言子をつけなくてもエラーになりません。

Python
x = 10

これは暗黙的な変数宣言を行った後に代入操作を行っていると言えます。

ただ、「変数を宣言することでデータ格納のためのメモリを確保し、それに対して作用を加えていくことでアルゴリズムを記述する」というのが、手続きプログラミングと考えると、初学者にはその感覚を定着させるため、常に変数宣言を明示的に行わせるのがよいと考えました。

Momonga
x = 10; // Name error

なお、Momonga のvarブロックスコープを形成します。

非エンジニアの方が書いたコードを見ると、スコープが意識されていないものをよく見ます。プロのプログラマーを目指す者としてはスコープの概念は最初からしっかり学ぶべき事項です。スコープも色々とあると混乱するので、唯一の宣言子であるvarが唯一のスコープであるブロックスコープを形成するという仕様です。

ただ、JavaScript にもvarがあり、こちらは関数スコープまたはグローバルスコープです。移行した際に混乱しそうですね・・・(笑)

なお、初期化をしない場合、null相当のnoneが入ります。

Momonga
var y;
y; // none

文(Statement)

文末にセミコロンは必須

セミコロン;は実行単位である文の区切りを示すものです。既出スニペットを見ていただくと分かる通り、文末には;が必ず付いています。

JavaScript では改行を伴う場合など、付与しなくてもいいですが、Momonga では、初学者が迷わないために選択肢をなくし、常に明示的に;を付与することを要求することにしました。

ただし、ブロック文にはセミコロンはつけられません。
ブロック文とは下記のような文をまとめたものを一つの文として扱うものです。

Momonga
{
  doSomething1();
  doSomething2();
}

js ではブロック文を含めた全ての文に;がつけられるという統一的なルールなのでシンプルさで劣りますね。どうしてこうしたのかは・・・忘れました。(笑)

空文(Empty Statement)は存在しない

JavaScript では空文が存在し、これは文として実行できます。

JavaScript
;

したがって、下記のような意図しないコードも JavaScript では構文的には正しくなります。

JavaScript
if (condition); {
  doSomething();
} // 常に実行される

空文は意図しない文を成立させてしまうおそれがあるので、Momonga では空文を許可しないようにしました。

Momonga
; // Syntax error

細かい仕様のようですが、昔、新人の Java のコードでこのような;が混入することで意図しないコードのコンパイルが通ってしまい、発見に少し苦労するということがあったのでこの仕様は意外と重要です。(笑)

制御文

一般的な構文

if 文

Momonga
var age = 99; // Enter your age!
var price;

if (age <= 3) {
  price = 100;
} else if (3 < age && age <= 9) {
  price = 300;
} else {
  price = 500;
}

for 文

Momonga
for (var i = 0; i < 10; i = i + 1) {
  if (i == 3) {
    continue; // Move on to the next iteration
  }

  if (i == 7) {
    break; // Break out of the loop
  }
}

while 文

Momonga
var x = 0;

while(x < 10) {
    x = x + 1;
    if (x == 3) {
        continue; // Move on to the next iteration
    }

    if (x == 7) {
        break;    // Break out of the loop
    }
}

常にブロック文を要求する

例えば、JavaScript の if...else 文のシンタックスは下記です。

if (condition)
  statement1

// With an else clause
if (condition)
  statement1
else
  statement2

したがって、下記は JavaScript では問題ありません。

JavaScript
if (true) doSomething();

しかし、複文になると、ブロックを付ける必要があります。

JavaScript
if (true) {
  doSomething1();
  doSomething2();
}

このようなシンタックスのため、JavaScript では単文の場合に、プログラマーによってコーディングスタイルが異なります。

JavaScript
if (true) {
  doSomething();
}

この点についても、Momonga では言語仕様レベルで選択肢をなくし、if 文や for 文、while 文といった制御文では常に、ブロック文を要求するようにしました。(eslint でいえばcurlyルール)

ちなみに、if 文の else if... については独立した定義は不要で else句 の後に if...else 文をとる再帰的な構造で対応できているというのが面白いと思いました。(これが elif とかだと独自の定義が必要)

関数の宣言と実行

Momonga
func factorial(n) {
    print(n);
    if (n == 0) {
        return 1;
    }
    return n * factorial(n - 1);
}
factorial(5); // 120

functionで宣言するほうが現代のメジャーな言語では多いですが、 Go 流にタイプ量の少ないfuncとしました。
Ruby や Rust と違い、明示的なreturnが必要です。

なお、JavaScript のようなHoistingはありません。

Momonga
add(1, 2); // Name error
func add(a, b) {
    return a + b;
}

「プログラムは上から実行され宣言したら使える」というほうが分かりやすいと思ったので、特に頑張って実装しようとも思いませんでした。

関数の構文としての論点はこのくらいかと思います。

組み込み関数

Momonga は以下の組み込み関数を提供しています。

  • print(): 標準出力への出力
  • len(): 配列または文字列の長さを返す
  • push(): 配列に要素を追加
  • pop(): 配列から最後の要素を取り出して返す

これらも最小限ですが、多くの基本的なアルゴリズムを記述できると思います。

2. 必要最低限の概念のみをもつ

オブジェクトの概念を出さない

初学者はアルゴリズムには順次、反復、分岐の制御構造があることをまず学びます。ここで、初学者に人気の Python の for ループは Iterator やオブジェクトの理解を前提としている点で初学者向きではないな、とずっと思っていました。

Python
for x in range(10):
  print(x)

これを厳密に理解するのであれば、オブジェクトや Iterator の概念は避けられません。

Momonga では「オブジェクト」の概念に触れずにプログラミングできるようにしています。それ故、下記のようなこちらもお馴染みの for ループの構文を採用しています。

Momonga
for (var i = 0; i < 10; i = i + 1) {
  print(i);
}

こちらのクラシックなスタイルはより手続き的であり冗長ではありますが、それ故に理解もしやすいかと思います。IT エンジニアの登竜門とされる IPA 基本情報技術者試験のアルゴリズムで頻出する for ループも

for (制御記述)
 処理
endfor

のような構文であり、こちらに近いです。

参考:IPA 試験で使用する情報技術に関する用語・プログラム言語など

Momonga のデータ型

結局、Momonga がサポートするデータ型は以下の通りです。

  • Integer: 整数
  • Float: 浮動小数点数(開発中)
  • Boolean: 真偽値(true/false
  • String: 文字列
  • Array: 配列
  • None: 値の不在を表す

配列の操作に関してもオブジェクトの扱いではないのでarr.len()のようなドット演算子ではなく、グローバルに登録された組み込み関数で下記のように操作します。

Momonga
var arr = [1, 2, 3];

arr[len(arr) - 1]; // 3

// Manipulate elements
push(arr, 4);
arr; // [1, 2, 3, 4]
pop(arr);
pop(arr);
arr; // [1, 2]

暗黙の型変換をしない

「データ型」は初学者が学ぶ最重要事項です。
ここで初学者を混乱させてしまうのが、暗黙の型変換です。

例えば、JavaScript では下記は普通に実行できます。

JavaScript
let x = 1;

if(x == "1") {
    console.log(x); // 1
}

初学者はこのようなプログラムを記述しても、自分が異なるデータ型を比較していることに気づいていないことも多いです。もちろん、厳密な等価演算子===を使うべきなのですが、そもそも等価演算子が複数あることが、初学者にはハードルなのです。

Momonga では暗黙の型変換は行わず、データ型が違う場合は型エラーとなります。等価演算子は==の一つであり、

Momonga
var x = 1;

if(x == "1" /* Type Error */) {
    print(x);
}

となります。

関数の特徴

関数については下記のような特徴があります。

  • ファーストクラス関数ではない: 関数を変数に代入したり、引数として渡したりできない
  • 関数リテラルがない: 無名関数やラムダ式はサポートしない
  • 明示的なreturnが必要: Ruby のような暗黙の return はない

これらの制約により、関数の概念がシンプルに保たれ、初学者が混乱することを防いでいます・・・と書けば聞こえがいいですが、多分この辺で力尽きてこれが正解になりました(笑)
ただ、初学者的には「関数は定義して呼ぶもの」というシンプルな理解があれば十分ではないでしょうか?

なお、クロージャは初学者には不要だと思うのですが、図らずも実装することになりました。それがなぜかは実装面の話になるので、また今度。

3. 環境構築不要で、オンライン実行環境がある

こちらは鋭意準備中です!!
この記事を書いていて、Playground で動かせるのがやはり一番伝わると思いました。

Rust に素敵なPlaygroundがあるのもそういうことでしょう。こういう感じのものを作りたいと思ってます。

おわりに

Momonga の仕様を考えることを通して、他言語の理解や、プログラミング言語そのものの理解を深めることができました。プログラミング能力でブレークスルーしたい方はぜひオレオレ言語の実装をおすすめします。

Special Thanks