この文書はKome言語の現在の仕様を整理するためのメモです。
厳密な言語仕様書ではないです
Kome は mochiOS 向けのアプリケーション開発を主目的としたプログラミング言語です。
GUI アプリケーションを簡潔に記述できることを重視しますが、GUI 専用言語にはせず、通常のアプリケーションロジックやシステムプログラミングにも利用できることを目指します。
構文は Rust に近いものを採用します。
ただし、Rust のように「ほぼすべてが式である」といった強い式指向の設計にはしません。
また、Rust の所有権・Clone・Copy などの複雑な概念を、そのまま通常の Kome プログラマに見せることは避けます。
Kome は静的型付け言語です。
以下を提供します。
- 型注釈
- 型検査
- 型推論
例:
let name: String = "Kome"
let count = 10
count の型は初期値から推論されます。
文末のセミコロンは任意です。
以下はどちらも有効です。
let x = 10
print(x)
let x = 10;
print(x);
通常の変数には let と var を使用します。
let は不変です。
let name = "Kome"
let は必ず宣言時に初期化する必要があります。
以下は無効です。
let name: String
以下は有効です。
let name: String = "Kome"
var は可変です。
var count = 0
count += 1
var は型注釈がある場合、未初期化宣言を許可します。
var value: String
未初期化状態の値を読み取ることはできません。
コンパイル時定数には const を使用します。
const PI: Number = 3.14159
詳細な compile-time evaluation の範囲は未決定です。
通常のアプリケーション開発では Number を使用します。
let a = 10
let b = 3.14
どちらも Number です。
システムプログラミング向けには固定幅数値型も用意します。
i8
i16
i32
i64
u8
u16
u32
u64
f32
f64
Number と固定幅数値型の間では暗黙変換を行いません。
let x: i32 = 10
let y: Number = x
このようなコードは型エラーになります。
明示的な変換を必要とします。
変換構文の詳細は未決定です。
文字列型は String 一種類です。
Rust の String と &str のような区別は、Kome の通常のコードには露出させません。
この方針は初期の型設計でも明示されています。
let message: String = "Hello"
真偽値型は bool です。
let enabled: bool = true
値が存在しないことを表すために null を使用します。
Optional 型は T? と書きます。
let name: String? = null
T? は概念的には、
T | null
です。
そのため、
let name: String = null
は型エラーです。
null check の後では型を絞り込みます。
if name != null {
print(name)
}
このブロック内では name を String として扱えます。
名前付き構造体は struct で定義します。
struct User {
name: String
age: Number
}
インスタンスは以下のように生成します。
var user = User {
name: "Alice"
age: 15
}
struct のフィールドは可変です。
user.age = 16
let で保持された struct のフィールド変更を許可するかどうかは未決定です。
Object literal は匿名 struct として扱います。
let user = {
name: "Alice"
age: 15
}
この値は、概念的には以下のような匿名型を持ちます。
{
name: String
age: Number
}
同じフィールド名・同じ型を持っていても、異なる object literal から生成された匿名 struct は別型です。
let a = {
name: "A"
age: 10
}
let b = {
name: "B"
age: 20
}
a と b は別型です。
再利用可能な同一型として扱いたい場合は、名前付き struct を定義します。
列挙型は enum で定義します。
enum Color {
red
green
blue
}
raw value を持たせることもできます。
enum HttpStatus {
ok = 200
notFound = 404
}
raw value を持つ場合、すべての case の raw value は同じ型である必要があります。
Rust の enum のような、各 case が異なる associated data を保持する機能は現時点では導入しません。
.blue のような構文を利用できます。
Text("Hello", color: .blue)
.blue は期待型から関連する値を解決します。
例えば color の期待型が Color なら、
.blue
は概念的に、
Color::blue
として解決されます。
現在の Komec でも DotIdent として AST に存在します。
関数は fn で定義します。
fn greet(name: String) -> String {
return "Hello, " + name
}
デフォルト引数を使用できます。
fn add(
a: Number,
b: Number = 0,
) -> Number {
return a + b
}
デフォルト引数の構文は、
name: Type = default
です。
UI Component は component で定義します。
component Counter() {
state count = 0
}
以前存在した bundle 構文は完全に廃止します。仕様議論でも bundle から component へ移行しています。
Component の外部入力は普通の parameter として定義します。
component UserCard(
name: String,
age: Number,
) {
...
}
以前検討されていた prop は使用しません。
state は Component 内部の状態です。
state count = 0
state には通常の型推論が適用されます。
state count = 0
state name: String = "Kome"
state は常に可変です。
state count = 0
count += 1
state が変更されると、それに依存する recipe が再評価されます。
以前の state let mut 形式は廃止し、state name = value に簡略化する方針になっています。
recipe は関数ではありません。
依存する状態が変更された際に再評価される処理単位です。
recipe counter: count {
...
}
依存関係付きの再評価単位として設計されていることは、初期仕様でも明示されています。
依存関係は基本的に自動判定します。
例えば、
recipe counter {
Text("Count: {count}")
}
で count を参照している場合、Compiler / Runtime はこの recipe が count に依存していることを自動的に判定します。
必要な場合は明示的に依存対象を指定できます。
recipe counter: count {
...
}
依存対象は同じ構文で記述しますが、対象の型によって意味が変わる場合があります。
Kome の UI は SwiftUI に近い宣言的 UI とします。
既存 UI の特定要素を命令的に変更するのではなく、
現在の状態なら UI はこうである
という形で UI を定義します。
この考え方は初期の仕様議論でも明確に採用されています。
@body は Kome 言語自体が意味を定義するものではありません。
ViewKit が定義する Attribute です。
Komec は通常の Attribute として解析します。
基本形は、
@body
let view = {
VStack {
Text("Hello")
}
}
です。
@body が付いた let の変数名は任意です。
@body
let content = {
...
}
も有効です。
@body の意味は ViewKit が解釈します。
UI の再評価時に、前回の Component と新しい Component をどのように同一視するかは未決定です。
固定された UI tree では構造上の位置などを利用できる可能性があります。
一方で、
for item in items {
ItemView(item)
}
のような動的 UI では、並び替え・追加・削除に耐えられる安定した identity が必要になります。
key や Attribute を使う方式などが候補ですが、具体的な仕様はまだ決定していません。
is は pattern matching の簡略構文です。
is x 1 => foo()
概念的には、
match x {
1 => foo()
_ => {}
}
に相当します。
is は match の糖衣構文として設計されています。
通常の match 構文も残します。
match x {
0 => print("zero")
1 => print("one")
_ => print("other")
}
is と match は両方使用できます。
通常の条件分岐も使用できます。
if count > 10 {
count = 0
} else {
count += 1
}
以下の制御構文を使用できます。
while condition {
...
}
for item in items {
...
}
break
continue
Closure を利用できます。
items.each(|item| print(item))
items.sort(|a, b| a > b)
capture semantics の詳細は未決定です。
List literal は、
let values = [1, 2, 3]
のように書きます。
List の型規則、異種型を許可するかどうかは未決定です。
文字列中に式を埋め込めます。
let count = 10
let message = "Count: {count}"
モジュール・型・名前空間の区切りには :: を使用します。
オブジェクトの field / method access には . を使用します。
File::open("test.txt")
file.read()
この区別は仕様議論でも採用されています。
モジュールやパッケージの読み込みには use を使用します。
use std::io
use viewkit::prelude
ローカルモジュールと外部パッケージの名前解決方法の詳細はまだ未決定です。
通常の Kome コードでは Rust の Clone / Copy / ownership を直接扱わせない方針です。
過去の仕様案では値を以下のように分類しています。
value
object
resource
unsafe
value: 通常のコピー可能な値object: GC / ARC 等で管理されるオブジェクトresource: File や Capability など単一所有を必要とするものunsafe: raw pointer や MMIO など
通常のアプリケーション開発では value と object を中心に扱い、システムプログラミング時のみ resource や unsafe を意識する設計を想定しています。
この分類の詳細はまだ未確定です。