※ 項目の名前は適当
※ 実装してみたい全ての機能を記述しているわけではありません
※ サンプルコードは古い構想を含んでいるかもしれません。注意して読んでください
-
楽しい言語をつくろう
- どんなに機能が充実していても楽しくなければ誰も使わない
-
読みやすい言語をつくろう
- どんなに機能が充実していてもわかりにくければ誰も学ばない
-
書きやすい言語をつくろう
- どんなに機能が充実していてもドキュメントがなければ誰も使えない
楽しいプログラム ≠ 読みやすいプログラム ≠ 書きやすいプログラム
「メモ帳でも書きやすい」は比喩表現じゃない
- クラス、インタフェース、クラスに所属しない関数を格納するための集合
- 上記3つは必ず何かのモジュールに属する
- モジュールのインポートは
import
を使い、局所的(scoped)なインポートも可能。前方参照は不可 import
のイメージは using に近い。C言語の#include
のようにコードを展開したりはしない- ファイル名がモジュールそのものとなる。ただしモジュール名に用いることができるのは識別子(identifier)と同じ文字列であり、OSやファイルシステムで使える名前がモジュール名として使えるとは限らない
import
構文でインポートできないというだけであり、例えば "123--.hoge$.lr" というファイル名ならばモジュール名も123--.hoge$\
となる
- 名前の衝突を起こしたものは自分と同じモジュールに属するものが優先される
- それでも衝突する場合は完全修飾名が必要
# A.lr
def hoge:
pass
# B.lr
def hoge:
pass
def fuga:
pass
# C.lr
import A
hoge # A.hoge
fuga # 実行時エラー: そのような関数or変数は存在しない
import B
import A # 実行時エラー: すでにモジュール A は読み込まれています
hoge # 実行時エラー: A.hoge と B.hoge の名前が衝突している
A.hoge # A.hoge
B.fuga # B.hoge
fuga # B.fuga
# D.lr
import B
def hoge:
pass
hoge # D.hoge
D.hoge # 実行時エラー: 自分のモジュールを完全修飾名で呼び出すことはできない
B.hoge # B.hoge
# E.lr
import D # モジュール B は読み込まれません
D.hoge # D.hoge
B.hoge # 実行時エラー: そのようなモジュールは存在しない
fuga # 実行時エラー: そのような関数or変数は存在しない
- モジュールをさらに包むような集合
- パッケージは階層化可能。階層構造はディレクトリの構造と同一
- リンク(ショートカット)は受け付けない。これはモジュールのファイルでも同様
- (標準ライブラリは lury パッケージ群)
# foo/A.lr
def hoge:
pass
# foo/B.lr
def hoge:
pass
def fuga:
pass
# main.lr
import foo.A
hoge # foo.A.hoge
import foo.B
foo.A.hoge # foo.A.hoge
foo.B.hoge # foo.B.hoge
fuga # foo.B.fuga
- D言語にある改名importと同じ
- 長い修飾名をファイル内でのみ有効な名前に改名する
- 名前の衝突回避にも使える
# main.lr
import fooa = foo.A
import foob = foo.B
fooa.hoge # foo.A.hoge
foob.hoge # foo.B.hoge
foo.A.hoge # 実行時エラー: そのようなモジュールは存在しない
foo.B.hoge # 実行時エラー: そのようなモジュールは存在しない
fuga # foo.B.fuga
foob.fuga # foo.B.fuga
- Lury はクラスベースのオブジェクト指向プログラミング言語です
- オブジェクトの設計図となるものがクラス、そのクラスを実体化したものがインスタンス
class
キーワードでクラスを宣言new
演算子でクラスをインスタンス化する
class Panda: # クラス宣言
pass
class Fox: # クラス宣言
pass
panda = new Panda # インスタンス化
fox = new Fox # インスタンス化
cat = new Cat # 実行時エラー: そのようなクラスは存在しない
panda_is_Panda = panda is Panda # true
fox_is_Fox = fox is Fox # true
panda_is_fox = panda is fox # false
- Luryでは通常、殆どのクラスの要素は外部のクラスからアクセスできる
- アクセスを禁止したい場合はアクセス修飾子をつけ、隠蔽する
- アクセシビリティは 何も指定しない、
public
、protected
、private
の4種類 - 対象によって修飾できるアクセシビリティが異なる。表は自身以外にアクセス可能なスコープを表し、n/a は修飾不可を表す
対象 | (何も指定しない) | public |
protected |
private |
---|---|---|---|---|
(モジュール) | 任意のモジュール | n/a | n/a | n/a |
モジュール関数 | 同一モジュールのみ | 任意のモジュール | n/a | n/a |
クラス | 同一モジュールのみ | 任意のモジュール | n/a | n/a |
関数 | 同一モジュールのみ | 任意のクラス * | サブクラス | 同一クラスのみ |
プロパティ | 同一モジュールのみ | 任意のクラス * | サブクラス | 同一クラスのみ |
インスタンス変数 | 同一クラスのみ | 任意のクラス * | サブクラス | 同一クラスのみ |
クラス変数 | 同一クラスのみ | 任意のクラス * | サブクラス | 同一クラスのみ |
[*] 属するクラスがpublic
である場合は任意のモジュールの任意のクラスからアクセス可能。未指定の場合は同一モジュールの任意のクラスでアクセス可能。
# animal.lr
public class Dog:
public def bark:
println('Bark!')
private def eat:
println('yum yum')
def sleep:
println('...')
class Cat:
public def meow:
println('Meow!')
private def eat:
println('yum yum')
def sleep:
println('zzz...')
def tryCreateAnimal:
dog = new Dog
cat = new Cat
dog.bark
cat.meow
dog.eat # 実行時エラー: eat関数にはアクセス出来ない
cat.eat # 実行時エラー: eat関数にはアクセス出来ない
dog.sleep
cat.sleep
# main.lr
import animal
dog = new Dog
cat = new Cat # 実行時エラー: Catクラスにはアクセス出来ない
dog.bark
dog.eat # 実行時エラー: eat関数にはアクセス出来ない
dog.sleep # 実行時エラー: eat関数にはアクセス出来ない
- クラスの宣言時に継承するクラスを指定できる
- 多重継承(複数のクラスを継承)することはできない
- 継承されたクラスをスーパークラス、継承したクラスをサブクラスという
- サブクラスはスーパークラスの
public
あるいはprotected
な関数、プロパティ、変数にアクセスできる (同一モジュールならばアクセシビリティ未指定の関数・プロパティにもアクセスできる) - スーパークラスは
super
キーワードで参照可能
class Animal:
def eat:
println('yum yum')
def sleep:
println('zzz...')
class Cat(Animal): # Animalを継承してCatを宣言
pass
animal = new Animal
animal.eat
animal.sleep
cat = new Cat
cat.eat # Catクラス内でeat、sleep関数を宣言していないが
cat.sleep # Animalを継承するのでそのまま使える
- スーパークラスを指し示すキーワード
- 関数のように呼び出すと、_呼び出した関数と同じ名前を持つスーパークラスの関数_を呼び出す
- これによりオーバーライドした関数は単に
super([引数])
とだけ指定すればオーバーライドされた関数を呼び出せる - コンストラクタがスーパークラスのコンストラクタを呼び出せる(委譲コンストラクタ)。これ以外にコンストラクタを直接的に呼び出す方法は
new
しかないため、通常の関数からコンストラクタを呼び出すことは不可能である
- これによりオーバーライドした関数は単に
- オブジェクトとして呼び出せる。
super.[メンバ名]
のようにアクセス可能 - ラムダ式や関数内に宣言された関数内での
super
はコンパイルエラー
class A:
def this:
println('class A constructor!')
def greet:
println('class A greet!')
class B(A):
def this:
super # (実際はこのsuperは省略可)
println('class B constructor!')
override def greet:
println('class B greet!')
super
b = new B
b.greet
# 出力:
# class A constructor!
# class B constructor!
# class B greet!
# class A greet!
- (Objectクラスを除く全てのクラスの)コンストラクタはスーパークラスのコンストラクタを呼び出して初期化しなければならない
- 委譲コンストラクタを行うために、コンストラクタは必ず1回、
super
によるスーパークラスのコンストラクタを呼びださなければならない - スーパークラスが引数を持たないコンストラクタを持つ場合、サブクラス側では
super
の呼び出しが省略できる- 省略した場合はサブクラスのコンストラクタの開始直後にスーパークラスのコンストラクタが呼び出される
- スーパークラスのコンストラクタが呼び出されない、2回以上呼び出される可能性がある場合はコンパイルエラーとなる
- ループの内側での委譲はできない。論理的にループしない構造でも配置はできない
- Objectクラスのコンストラクタは引数を持たない
class A:
def this:
println('class A constructor!')
class B(A):
def this(foo):
println('class B constructor!')
class C(B):
def this(foo):
println('class C constructor!')
super(foo)
c = new C
# 出力:
# class C constructor!
# class A constructor!
# class B constructor!
class A:
def this(foo):
println('class A constructor!')
class B(A):
def this(foo): # コンパイルエラー: スーパークラスのコンストラクタが呼ばれていない
println('class B constructor!')
class C(A):
def this(foo):
println('class C constructor!')
times 5:
super(foo) # コンパイルエラー: ループの内側で委譲はできません
- 継承を禁止したい場合は
sealed
キーワードを使う sealed
キーワードで修飾されたクラスは継承できないsealed
キーワードで修飾された関数・プロパティはオーバーライドできない (オーバーライド参照)
sealed class Animal:
def eat:
println('yum yum')
def sleep:
println('zzz...')
class Cat(Animal): # コンパイルエラー: Animalクラスを継承できない
pass
- ポリモフィズムを実現する
- オーバーライドはスーパークラスの動作を上書きする
- 関数・プロパティのみオーバーライドでき、オーバーライドしたものは
override
キーワードをつける sealed
キーワードが付けられたメンバは、そのサブクラスでオーバーライドできない- 無論、
override
とsealed
を併用できる(順番は無関係) sealed
がない全ての関数・プロパティは仮想関数・仮想プロパティです
class Animal:
def eat:
println('yum yum')
def sleep:
println('zzz...')
class Human(Animal):
override def eat:
println('ムシャムシャ')
sealed override def sleep:
println('グーグー')
#override def greet: # コンパイルエラー: greetという関数はスーパークラスに存在しない
# println('こんにちは')
class HumanLikeAnimal(Human):
override def eat:
println('バリバリ')
#override def sleep: # コンパイルエラー: sleepはこれ以上オーバーライドできない
# println('スヤスヤ')
animal = new Animal
animal.eat # yum yum
animal.sleep # zzz...
human = new Human
human.eat # ムシャムシャ
human.sleep # グーグー
humanLike = new HumanLikeAnimal
humanLike.eat # バリバリ
humanLike.sleep # グーグー
class A:
def a:
println('A.a')
def call_a:
this.a
class B(A):
def a:
println('B.a')
p = new B
p.call_a # B.a
プログラム出典: Pythonのメソッドはオーバーライドされているのか
- 実装を持たないメンバを持てるクラス
- 抽象メンバをひとつでも持つクラスは抽象クラスに必ずなる
- 抽象メンバを持たない抽象クラスは作れる
- 抽象クラスはインスタンス化できない
- サブクラスが抽象クラスの抽象メンバを実装する
- 抽象メンバにも契約が使える
abstract
キーワードで抽象クラス/メンバを作れる- 抽象メンバは実装を持てない
abstract class Animal:
abstract def greet # 実装を持たない関数
abstract def eat(food):
in:
enforce(food != null) # 契約は書ける
abstract property age(get) # プロパティも抽象化可能
class Human(Animal):
override def greet:
println('Hello!')
override def eat(food):
println("Yum yum (#{food})")
override property age(get, private set) # 自動実装
def this(age):
this.age = age
human = new Human(23)
human.greet
human.eat('bread')
- 実装を一切持たず、public な関数およびメソッドを宣言をする
- public以外のメンバは書けない。厳密にはpublicが前提のため
public
とアクセシビリティを記述することもできない
- public以外のメンバは書けない。厳密にはpublicが前提のため
- 契約は書けるが、実装本体は書けない
- ただし不変条件は書けません
- インタフェースはインスタンス化できない
- クラスがインタフェースを「実装」する。クラスはインタフェースをいくつでも実装できる
- インタフェースは必要でない。ダックグタイピングによって同名の関数やプロパティにはアクセスできる
- インタフェースは契約を利用するため、および
is
による実装判定のために用いる部分が大きい
- インタフェースは契約を利用するため、および
- インタフェースはインタフェースを継承できる
- インタフェースAを継承するインタフェースBを実装する場合、Bを実装する記述をすればAも実装することとなる。また、この場合は
A, B
と記述しても良い
- インタフェースAを継承するインタフェースBを実装する場合、Bを実装する記述をすればAも実装することとなる。また、この場合は
- インタフェースの命名に規則性はない(が、クラスとの混同を避けるためにプレフィクス
I
を使用する)
interface IFoo:
def foo(message):
in:
assert(message is String)
out(res):
assert(message is String)
property foofoo(get, set)
interface IBar(IFoo):
def bar(message):
in:
assert(message is String)
out(res):
assert(message is String)
property barbar(get)
class FooBar(IBar):
def foo(message):
println('foo: ' ~ message)
def bar(message):
println('bar: ' ~ message)
property foofoo(get, set):
var _foofoo
property barbar(get, private set)
def this(barbar):
this.barbar = barbar
foobar = new FooBar(42)
foobar.foofoo = 5
foobar.foo('first message')
foobar.bar('second message')
- インタフェースがなくても、同名のメンバにアクセスできる
- このとき、引数はチェックされない。当然引数不一致もありえる
class Duck:
def sound:
println('quack')
class Dog:
def sound:
println('bark')
def test(animal):
animal.sound
test(new Duck) # quack
test(new Dog) # bark
Object
。全てのクラスclass
の共通のスーパークラスである- ユーザが継承を行わない場合、このクラスが暗黙的に継承される
Object
クラスはなんらかのクラスを継承しない- 必ず具象クラスである
- 全てのクラスはこのクラスと同じメンバを継承する。ゆえに
Object
クラスと同名の関数は定義できない(オーバーロードは可能)
Function
。関数、ラムダ式はこの関数型のインスタンスである- 関数呼び出しが可能な唯一の型である
- 関数の名前、アノテーション、引数リスト情報などのプロパティ情報を持つ
String
。文字列を表すコンテナオブジェクトである- 文字要素は変更不可。固定長。この点はタプルと同様
- タプルと異なる点は文字列型は内部に文字データしか格納しない点
- エンコードは必ずUnicode
- 文字列の長さ(文字数)のプロパティを持つ
- 文字要素は変更不可。固定長。この点はタプルと同様
Number
。数値を表す抽象クラスである- 値型
- 表現できる最大値、最小値、ゼロ、1、-1をプロパティとして持つ
- 各種数値型の派生クラスに変換できるような抽象関数を持つ
- 数値表現で使用しているバイト数の情報も持つ
Integer
。整数値を表すクラス- 少なくとも64bit符号付き整数の範囲を持つ
Float
。浮動小数点数を表す抽象クラス- 正の無限大、負の無限大、(型としての)イプシロン、NaNのプロパティを持つ
- 少なくとも倍精度(64bit)の精度は持つ
Real
。Float
型の具象クラスとしての存在
Complex
。実数と虚数を持つ浮動小数点数型- 複素演算を持つ
- キーワード
enum
で表され、Enum
抽象クラスの派生クラスである- 定数をグループ化し、各種関数を提供する
- C#よりもJavaの列挙型に近い
- 列挙型は関数、コンストラクタを定義可能
Enum
クラスは演算子オーバーロードなどの機能を提供するenum
キーワードを列挙型の内部に使うと列挙値を記述可能- 列挙値は
public static sealed
相当。アクセシビリティは記述不可 - 列挙値の代入は自列挙型でなくてもよい
- 列挙値は
enum Color:
enum Alpha = new Color(0, 0x00000000, 'Transparent')
enum Red = new Color(1, 0x00ff0000, 'Red')
enum Green = new Color(2, 0x0000ff00, 'Green')
enum Blue = new Color(4, 0x000000ff, 'Blue')
property flag (get, private set)
property bit (get, private set)
property name (get, private set)
private def this(flag, bit, name):
this.flag = flag
this.bit = bit
this.name = name
colors = Color.Red | Color.Green # {Color.Red | Color.Green}
colors.sum(c => c.flag) # 3
colors -= Color.Red # {Color.Green}
colors & Color.Red # {}
Color.Green in colors # true
Boolean
。二値を表現するクラス- 値型
true
またはfalse
の二値論理を表す
- 複数の要素を格納できるオブジェクトである
- Luryでは言語としてリスト、タプル、ハッシュ、セット そして 文字列がコンテナオブジェクトとして扱うことができる
- 以下の条件を満たすものがコンテナ(IContainer)である
- コンテナに指定されたオブジェクトが存在するか確認する(contain)
- 要素数を取得する(count)
- 自身のコンテナの要素を列挙するイテレータを生成する(IIterable.iterate)
- 以下の条件を満たすものが書き換え可能コンテナ(IWritableContainer)である
- 要素をクリアする(clear)
- 要素を追加する(add)
- 以下の条件を満たすものがインデクスアクセス可能コンテナ(IIndexReadable)である
- 指定インデクスの要素を取得する(get)
- 指定されたオブジェクトがどのインデクスに存在するか取得する(indexOf)
- 以下の条件を満たすものがインデクス書き換え可能コンテナ(IIndexWritable)である
- 指定インデクスの要素を変更する(set)
- 指定インデクスの要素を削除する(remove)
インタフェース | リスト | タプル | ハッシュ | セット | 文字列 |
---|---|---|---|---|---|
IIterable | ○ | ○ | ○ | ○ | ○ |
IContainer | ○ | ○ | ○ | ○ | ○ |
IWritableContainer | ○ | × | ○ | ○ | × |
IIndexReadable | ○ | ○ | ○ | × | ○ |
IIndexWritable | ○ | × | ○ | × | × |
- オブジェクトを列挙するオブジェクト
- 以下の条件を満たすものがイテレータ(IIterator)である
- 現在の要素を返す(current)
- 次の要素を指し、次の要素があるなら
true
を返す(moveNext)
- 以下の条件を満たすものがイテレート可能オブジェクト(IIterable)である
- イテレータを生成する(iterate)
- コンテナのバージョンを取得する(version)
- これによりイテレータがコンテナの変更を検知できる
- ジェネレータ
yield
でイテレータを簡単に作ることができる
- 演算子の動作を定義する
- 乱用禁止。記号の意味を大幅に変更するような定義は非推奨
- 注意深い定義が必要。安易な定義は循環定義のおそれがある
public static def operator [演算子]
の形式で定義可能- (WIP)定義可能な演算子は以下の通り
演算子 | シグネチャ | 返り値の型 |
---|---|---|
++(前置) | operator ++ (x) |
任意 |
--(前置) | operator -- (x) |
任意 |
+(符号) | operator + (x) |
任意 |
-(符号) | operator - (x) |
任意 |
~(ビット) | operator ~ (x) |
任意 |
! | operator ! (x) |
任意 |
** | operator ** (x, y) |
任意 |
-
| `operator * (x, y)` | 任意
/ | operator / (x, y)
| 任意
% | operator % (x, y)
| 任意
~(結合) | operator ~ (x, y)
| 任意
// | operator // (x, y)
| Integerを推奨
+(加算) | operator + (x, y)
| 任意
-(減算) | operator - (x, y)
| 任意
<< | operator << (x, y)
| 任意
>> | operator >> (x, y)
| 任意
== | operator == (x, y)
| Booleanを強制
!= | operator != (x, y)
| Booleanを強制
< | operator < (x, y)
| Booleanを強制
> | operator > (x, y)
| Booleanを強制
<= | operator <= (x, y)
| Booleanを強制
>= | operator >= (x, y)
| Booleanを強制
& | operator & (x, y)
| Booleanを強制
^ | operator ^ (x, y)
| Booleanを強制
| | operator | (x, y)| Booleanを強制
- クラス、関数、プロパティ、引数、返り値、メンバ変数にメタ情報を付与する
- いくつでもアノテーションをつけられる
- 言語が用意するアノテーションによっては動作が変わる重要なものもある
<[付与対象:][アノテーション][引数]>
の形式で付与可能- 付与対象は
class
,def
,property
,var
,enum
,get
,set
,return
が指定でき、このうちclass
,def
,property
,var
,enum
は省略可能 - アノテーションは
Annotation
クラスの派生クラス名を指定する - 引数は省略可能
- 一行で複数のアノテーションを指定可能。
<target1: A, B, target2: C>
の形式。A, Bはtarget1が対象、Cはtarget2が対象。
- 付与対象は
Annotation
クラスを派生して任意のアノテーションを定義可能- 以下のアノテーションは言語動作上重要な意味を持つ
BuiltIn
- 言語にビルトインされているクラスであり、単体テスト時に再帰を禁止するAsValue
- クラスのインスタンスは値型として処理されるDebugOnly
- デバッグ実行時のみ関数を実行するIntrinsic
- 言語基盤が関数の実装を提供するAllowedRecurcive
-BuiltIn
アノテーションがつけられたクラスで再帰を例外的に許可する
<BuiltIn>
public class Assert:
<DebugOnly>
<Intrinsic>
public static def assert(condition,
lazy message = nil : String,
lazy exception = nil : Exception,
file = reflect(file) : String,
line = reflect(line) : Integer):
pass
<BuiltIn>
public class Enforce:
<Intrinsic>
public static def enforce(value,
lazy message = nil : String,
lazy exception = nil : Exception,
file = reflect(file) : String,
line = reflect(line) : Integer) -> value:
pass
sealed
キーワードをメンバ変数に適用するとその変数は直接的に初期化またはコンストラクタによる初期化以外で代入ができなくなる- Javaの
final
、C#のreadonly
、D言語のimmutable
と同等の機能 - あくまで封印されるのは変数の値、参照のみである。参照先のオブジェクト本体の封印はチェックされない
- 自動実装プロパティに適用可能
- バッキングフィールドを指定する場合は
sealed var
、しない場合はsealed set
と指定する。どちらにも指定が必要
- バッキングフィールドを指定する場合は
enum
キーワードはpublic static sealed
相当
class Color:
property name (get, private sealed set)
property value (get, private sealed set)
def this(name, value):
this.name = name
this.value = value
def test:
this.name = nil # 実行時エラー: nameは変更不可
- C# にあるものと同じ機能を持つ
- 実際は関数を生成する糖衣構文。同じ名前の関数があると使えない
# getter のみでも可能
var age
property Age:
get:
return this.age
# アクセサにアクセシビリティを入れる
# 変数 value は自動実装 される
var age
property Age:
get:
return this.age
private set:
if value >= 0:
this.age = value
# 契約も書ける。ただしプロパティに一組のみ
property Age:
in:
assert(value >= 0)
out(res):
assert(value >= 0)
get:
return this.age
private set:
this.age = value
# これは以下のように省略可能(自動実装)
property Age (get, private set):
in:
assert(value >= 0)
out(res):
assert(value >= 0)
property Age (get, set)
# 以下と同じ意味のコードが生成される
var __Age
def Age(value):
this.__Age = value
def Age():
return this.__Age
property Age (get, set)
# デフォルト値の指定 (C# 6.0風)
property Age (get, set) = 42
# static なプロパティ
static property Age (get, set)
# protected なプロパティ
protected property Age (get, set)
# 組み合わせ (順番はどちらでもいい)
protected static property Age (get, set)
# アクセサにアクセシビリティを入れる
property Age (get, private set)
# コンパイルエラー。どちらかはプロパティと同じアクセシビリティが必要
property Age (private get, private set)
# コンパイルエラー。プロパティのアクセシビリティ以下の制約が必要
private property Age (public get, set)
# コンパイルエラー。setterがないため自動実装不可。getterが無い場合も同様
property Age (get)
- プロパティを手動で記述する場合に限りラムダ記法でアクセサを記述可能
var age
# 普通のプロパティ
property Age:
get:
return this.age
private set:
this.age = value
# ラムダ記法(っぽい)のプロパティ
property Age:
get => this.age
private set => this.age = value
- プロパティを自動実装する場合のみ、バッキングフィールドを直接指定できる
- バッキングフィールドはインスタンス変数に、静的プロパティならばクラス変数になる
- このため同じクラスメンバからアクセスが可能
- バッキングフィールドはアクセシビリティが指定でき、無指定の場合は
private
となる static
は指定できない(プロパティ自体に依存する)- イミュータブルキーワードも指定できる。その場合はset関数もコンストラクタ以外から呼ばれると失敗する
- 存在意義はイミュータブルなバッキングフィールドを持ちたいとき、プロパティの不変条件を記述したいときなど
- 不変条件内ではpublicなプロパティを記述することはできない。不変条件からはpublicな関数を呼べないため
- publicなプロパティを不変条件を記述する義務/権利があるのはプロパティのバッキングフィールドを記述したクラスと考える
- ~~抽象プロパティでバッキングフィールドを使うことはできるが、~~継承先でプロパティを具象化した際に再度バッキングフィールドを記述することはできない
- 抽象クラス内の通常のプロパティではバッキングフィールドを 記述できる
- しかし、抽象プロパティでは 記述できない
- インタフェースではバッキングフィールドは 記述できない
property hoge (get, set):
var _hoge # バッキングフィールド(private)
property hoge (get, protected set):
var _hoge # setterのアクセシビリティに依らずprivate
property hoge (get, private set):
public var _hoge # public
static property hoge (get, private set):
var _hoge # バッキングフィールドも static
property hoge (get, set):
var _hoge = 42 # 初期値の指定も可能
- 関数とその呼び出し元が負うべき制約条件をコード内に記すもの
- 事後条件、事前条件、そしてクラス不変条件がある
- 事後条件
in
は関数の最初に(ロジックが始まる前に)チェックが行われる。 関数の呼び出し元が負うべき制約条件 - 事後条件
out
は関数の最後にチェックが行われる。 関数が負うべき制約条件 - クラス不変条件
invariant
関数はクラスが常に満たす条件を記述する。これはコンストラクタ完了後、publicな関数の呼び出し前と後にチェックされる- クラスフィールドに作用した場合のみチェックするのもいいかも
これらにより、Luryは関数の引数の型、返却値の型をチェックする(かも)。契約がない場合、推論で型を決定する- Luryは動的型付けのため、この記述は不適当。ただし実行前の不適切な型の使用の際の警告には十分使える話
- 制約条件を持つメンバ(関数またはプロパティ)が継承され、オーバーライドされると同じメンバに複数回制約条件が記述されることがある。このとき、
- 事前条件は いずれかの 事前条件 が成立すれば良い
- これは ORの関係 であり、オーバーライドすることによって制約が緩くなると例えられる
- 事後条件は すべての 事後条件 が成立しなくてはならない
- これは ANDの関係 であり、オーバーライドすることによって制約が厳しくなると例えられる
- 事前条件は いずれかの 事前条件 が成立すれば良い
def Decorate(str):
in:
assert(str is string)
assert(str != nil)
out(res):
assert(res is string)
assert(res != nil)
return "Message: #{str}\n"
Decorate('Hello!')
class A:
def test(i):
in:
assert(i == 2)
return i
class B(A):
override def test(i):
in:
assert(i == 3)
return i
b = new B
b.test(2) # B.testの事前条件には違反するが、A.testには違反しない
b.test(3) # A.testの事前条件には違反するが、B.testには違反しない
class A:
def test(i):
out(res):
assert(i > 0)
return i
class B(A):
override def test(i):
out(res):
assert(i > 1)
return i
b = new B
b.test(2) # A.test、B.testのすべての事後条件に違反しない
b.test(1) # 実行時エラー: B.testの事後条件に違反する
b.test(0) # 実行時エラー: A.test、B.testの事後条件に違反する
class Size:
property X (get, set)
property Y (get, set)
def this(x, y): # コンストラクタ
this.x = x
this.y = y
def GetArea(): # 通常の関数
return this.x * this.y
invariant: # クラス不変条件。括弧は不要
assert(x >= 0)
assert(y >= 0)
- 引数および返り値の型に対してメタ情報を特別に付与し、IDEや契約のヒントとする
- 引数名の後に
:
、引数リストの直後に->
を続けることで記述できる- 引数には型名(クラス名)を、返り値には型名または引数名をつけられる
- 型名の場合は、
nil
でないときに それと同じ型とis
で判定するassert
式を自動生成する - 引数名の場合は、その引数と返却値が同じ型であるかを判定する
assert
式を自動生成する - プロパティに対しては
get
とset
の直後に:
が、どちらも同じ場合に限り->
で指定可能。両者の併用はコンパイルエラー - コンストラクタの返り値には指定不可
- リリース実行時には型アノテーションのチェックは行われない
class Color:
property name (get, private sealed set) -> String
property value (get, private sealed set) -> Integer
def this(name: String, value: Integer):
this.name = name
this.value = value
#
# 以上のコードは以下のコードと等価になるよう変換される
#
class Color:
# バッキングフィールド
private sealed var _`property_name_`backingField
private sealed var _`property_value_`backingField
# プロパティ
def _`property_name_`get:
out(res):
assert(res == nil || res is String)
return this._`property_name_`backingField
def _`property_name_`set(value):
in:
assert(value == nil || res is String)
this._`property_name_`backingField = value
def _`property_value_`get:
out(res):
assert(res == nil || res is Integer)
return this._`property_value_`backingField
def _`property_value_`set(value):
in:
assert(value == nil || res is Integer)
this._`property_value_`backingField = value
# コンストラクタ
def this(name, value):
in:
assert(name == nil || name is String)
assert(value == nil || value is Integer)
this._`property_name_`set(name)
this._`property_value_`set(value)
@
を使うとアトリビュート(属性)をひとまとめにして指定ができる- アトリビュートのグルーピング、長い場合の省略などが可能
- C++やDの
[アトリビュート]:
と同じ - 指定可能なものは以下の通り
- アクセシビリティ:
public
,protected
,private
extended
,override
,sealed
,static
- アクセシビリティ:
- 以下のものは指定できません
def
,var
,class
,interface
,enum
など
- 指定を解除したいときは単に
@
を使う - 意味的に指定ができない場面ではコンパイルエラーとなる(文法的にはOK)
- アトリビュートを二重に指定することはできない
@public
class Hoge:
@public
def func1:
pass
def func2:
pass
@private
def func3:
pass
@static
def func4:
pass
@public static
def func5:
pass
hoge = new Hoge
hoge.func1 # OK: public
hoge.func2 # OK: public
hoge.func3 # NG: private
Hoge.func4 # OK: 同じモジュールのため参照可能
Hoge.func5 # OK: public
- プログラムの動作に全く影響しない注釈を記述する
- 単一行コメントと複数行コメントがある
- 単一行コメントは
#
で開始され、改行文字を含まない行末までがコメント化される - 複数行コメントは
###
で開始され、###
で終了する。開始と終了は両方ペアである必要がある
- 単一行コメントは
- Luryのガイドラインとして
##
をドキュメンテーションとして推奨する- 文法的には単一行コメントと同じ
- Doxygenなどがこれを読み取り、ドキュメント化することを期待する
# これはコメントです
answer = 42 # ここもコメントです
###
複数行コメントです
###
###
ネストはできません
↓ここでコメントが一旦切られてしまいます
###
###
###
answer = ### この部分がコメントです ### 42
- ただの関数へのエイリアスとなっている
- 同名の関数名はつけられる。例)任意のクラスで
print
関数は定義できるが、単にprint()
と呼び出すと組み込み関数が呼ばれる
- 同名の関数名はつけられる。例)任意のクラスで
- 組み込み関数が実際に属するモジュールはインポートしなくても使える
- 埋め込み関数と考えることもできるが、処理系実装側は必ず実装しなければならない関数とも捉えられる
- (ライブラリの設計も考えるべきであるため、以下に示すモジュール名などは仮のもの)
関数名 | 実際の関数(仮) | 説明 |
---|---|---|
print |
lury.io.Console.write | 標準出力に文字列を出力する |
println |
lury.io.Console.writeLine | 標準出力に文字列を出力し、改行文字を最後に出力する |
assert |
lury.contract.Assert.assert | アサーション(契約) |
enforce |
lury.contract.Enforce.enforce | エンフォース(契約) |
typeof |
lury.reflect.Type.of | 指定されたオブジェクトのタイプオブジェクトを取得する |
lock |
(未定) | 指定されたオブジェクトをロックオブジェクトとして同期処理 |
list |
lury.container.List.from | リストを生成する |
tuple |
lury.container.Tuple.from | タプルを生成する |
hash |
lury.container.HashMap.from | ハッシュを生成する |
set |
lury.container.Set.from | セットを生成する |
- 前提として引数が無い場合のみ(0個)、関数の引数を省略可能
- 宣言側での括弧の省略
- 次のものは括弧を 省略できます : 通常の関数、コンストラクタ
- 次のものは括弧を 省略できません : 引数を持つ関数、拡張クラスの関数、ラムダ記法の関数(引数が0個ないしは1個の場合を含む)
- 次のものは括弧を 常に省略します : クラス不変条件(
invariant
)、単体テスト(unittest
)
- 呼び出し側での括弧の省略
- 次のものは括弧を 省略できます : 通常の関数、コンストラクタ(
new
使用時)、拡張クラスの関数(第一引数が主語となる場合のみ) - 次のものは括弧を 省略できません : 引数を持つ関数(オプション引数を除く)、2変数以上の拡張クラスの関数
- 次のものは括弧を 常に省略します : プロパティ
- 次のものは括弧を 省略できます : 通常の関数、コンストラクタ(
def hoge: # 括弧の省略可能
pass
def fuga(value = 'yes'):
println(value)
def foo(func):
func # 括弧の省略可能(引数が必要なら実行時エラー)
def bar:
return () => 1 + 2 + 3
hoge # 括弧の省略可能
fuga
foo(hoge) # 注意! hoge関数は実行されて返り値がfoo関数の結果となる
# hoge関数は何も返さないので実行時エラー
foo(ref hoge) # foo関数にhoge関数の参照が渡される
# 実行時エラー。この場合の関数参照の括弧はつけてはいけません
# foo(ref hoge())
# この場合は括弧が必要
fuga(ref bar()) # 出力: 6
- クラスのコンストラクタは
this
を使う- 実はC#は委譲コンストラクタで自クラスのコンストラクタを呼び出すのに
this
を既に使ってたりする
- 実はC#は委譲コンストラクタで自クラスのコンストラクタを呼び出すのに
class Size:
property X (get, set)
property Y (get, set)
def this(x, y): # コンストラクタ
this.x = x
this.y = y
def this():
this(0, 0) # 委譲コンストラクタ
- 単体テストを記述できる。言語が単体テストの構文を用意する
- unittestは単なる関数。クラス内にも書けるし、グローバルな関数としても書ける。interfaceには書けない
- unittestは何回でも書ける。ただし関数の中にunittestは書けない。もちろんunittestの中にunittestは書けない
- クラスの中に書けるおかげで、privateな関数の単体テストも可能
- ユーザコードからunittestは呼び出せない。実行時のLuryのコマンドラインに単体テストを実行するというコマンドを指定した時のみ実行される。通常時は実行されない
- unittestの実行順は未定義。unittestが互いに依存してはいけないし、実行順に依存してもいけない
- 単体テスト時は契約もチェックされる
def Decorate(str):
in:
assert(str is string)
assert(str != nil)
out(res):
assert(res is string)
assert(res != nil)
return "Message: #{str}\n"
unittest: # 単体テスト。括弧は不要
str1 = ''
str2 = 'test'
str3 = nil
assert(Decorate(str1) == 'Message: ')
assert(Decorate(str2) == 'Message: test')
Assert.fail(Decorate(str3))
- (破棄)
- この方法では第一引数の型を制限できない。リリース時にassertは実行されないため。このサンプルにあるassertの用法はassertの意義と合致しない。
- 事前条件による引数の型チェックは(コンパイラとしては)行わない。
- 事前条件がなかった場合、全てのオブジェクトから拡張メソッドにアクセスできることとなり、記述の効率も実行の効率も悪い。
- よって拡張メソッドは破棄。代わりに 拡張クラス によって同じことを実現する。
- 大抵の場合、staticな関数またはクラスに属していないグローバル関数は第一引数を主語にして実行可能
- 特に明示すること無く C# の拡張メソッド的な使用法で呼び出せる
# グローバル関数
def ConnectString(s1, s2):
in:
assert(s1 is string)
assert(s2 is string)
return s1 ~ s2
# C言語的呼び出し
ConnectString('program', 'ming')
# 拡張メソッドっぽい呼び出し
'program'.ConnectString('ming')
- C# の拡張メソッド的なもの。既存のクラスのオブジェクトを主語にして使用できる
extended class [既存のクラス]
の形式で拡張クラスを宣言できる- インタフェースの場合は
extended interface [既存のインタフェース]
- 列挙型の場合は
extended enum [既存の列挙型]
- 抽象クラスの場合は
abstract
を付けずにextended class [既存のクラス]
- インタフェースの場合は
- 拡張クラスは通常のクラスとは異なる性質を持つ
- クラスを継承できない
- インタフェースを実装できない
- インスタンス化できない
- よってコンストラクタとインスタンス変数、通常のメソッドは持てない
- クラス変数は持てない
- 拡張メソッド以外の通常のメソッドは持てない
- 抽象メンバは持てない
- プロパティは持てない
- public以外のメンバを持てない
- 拡張クラスで持てるメンバは以下のもののみ
- 拡張メソッド。これは自動的に
public
かつstatic
なメソッドとなる - unittest
- 拡張メソッド。これは自動的に
- 拡張メソッドは通常のメソッドとしても呼び出せる
# Stringの拡張クラス
extended class String:
extended def connect(s1, s2):
in:
enforce(s2 is string)
return s1 ~ s2
# C言語的呼び出し
String.connect('program', 'ming')
# 拡張メソッドっぽい呼び出し
'program'.connect('ming')
- C#と同じ、参照アクセス可能な引数。参照渡しが可能
ref
は呼び出し側で初期化が完了していると保証されているものout
は呼び出された側で初期化されると保証されるもの- D言語と同じく、関数に入った時点で暗黙的にデフォルト値が代入される
- 呼び出し側に ref/out は付けない(呼び出し側の ref は関数参照, out は引数展開)
def Add1(ref value):
value + 1
def Add2(out value):
value + 1
i = 2
Add1(i) # 3
Add2(i) # 1
- いわゆる遅延評価。利用側から見ると遅延評価されているように見える
- 関数側から見ると、利用側が渡した引数の値を生成する関数の形で受け取られる
- 単なる関数参照なので返り値のない関数も渡せる
def printIf(cond, lazy func):
if cond:
println(func # ここで下の第二引数が評価される
for v in -4.0.stepTo(4.0):
printIf(v >= 0.0, "sqrt(#{v}) = #{Math.sqrt(v)}") # 第二引数は遅延評価される
- これは下のような処理に等しい
- lazy によって利用側でいちいちクロージャを作る必要がない
def printIf(cond, func):
if cond:
println(func)
for v in -4.0.stepTo(4.0):
closure = () => "sqrt(#{v}) = #{Math.sqrt(v)}"
printIf(v >= 0.0, closure)
- クロージャなので以下のようなことももちろん可能
def repeat(count, lazy value):
while count-- > 0:
println(value)
counter = 1
repeat(5, counter++) #出力: 1 2 3 4 5
- JavaやC#とは異なり、関数はオーバーロードを作れない。これは拡張クラスやコンストラクタも同様です
- 一因にrefによる関数参照でオーバーロードを区別して参照できない
- 一つの関数はただ一つのシグネチャ(名前と引数リスト)を持つ
- オーバーロードと同様のことをしたい場合はオプション引数を使う
- 関数を関数オブジェクトとして参照する場合は
ref
を使う ref [関数名]
として使用するref [関数呼び出し]
は許容しない!(サンプル参照)
- プロパティの場合、
ref get [プロパティ名]
またはref set [プロパティ名]
で指定するref [プロパティ名]
は使えません- プロパティの実体は関数であるため、このようにプロパティを関数参照した場合でもプロパティにアクセスできる
- getterは引数なしの関数である
- setterは1つの引数valueをとる関数である
- プロパティを関数参照した場合、通常のプロパティのように直接代入ということはできなくなる
def f(func, args..):
return func(out args)
def div(x = 1):
return y => x / y
f(div, 5) # 0.2 : div(1)の結果がfに渡される
f(ref div, 10) # y => 10 / y : div関数の参照がfに渡される
f(ref div(10), 2) # 文法エラー: div(10)は関数名ではなく関数呼び出し
p = div(10) # y => 10 / y
f(ref p, 2) # 5 : p の参照が f に渡される
class Foo:
property bar (get, set)
def test(propget, propset):
val = propget
propset(val + 1)
foo = new Foo
foo.bar = 5
test(ref get foo.bar, ref set foo.bar)
println(foo.bar) # 6
- 関数は可変長の引数をとれる
- 宣言側は
..
を指定することで可変長引数であることを宣言可能- 引数リストの最後の引数のみに指定可能
- 加えて、複数の可変長引数を指定することはできない
- 可変長引数は 引数オブジェクト となる。名前付き引数はペアオブジェクトとして含まれる
- 拡張関数の第一引数には使えない
- 呼び出し側は通常の呼び出し方法で呼べる
- コンテナの引数展開も可能
- 可変長引数の名前を指定してコンテナを渡すことも可能
def printArgs(args..): # 可変長引数
for a in args: # 通常のコンテナと同じ扱い
print(a.toString ~ ' ')
else:
println
printArgs(1, 2, 3) # 1 2 3 : 通常の呼び出し
printArgs(1..4) # 1 2 3 : 範囲式として渡す
printArgs([1, 2, 3]) # 1 2 3 : リストとして渡す
printArgs(args: 1..4) # 1 2 3 : 名前付き引数
printArgs(1, args: 1..4) # 実行時エラー: 引数の数が一致しない
printArgs(args: 1..4, 1) # 実行時エラー: 引数の数が一致しない
printArgs(1 + 2, iwate: 'morioka', tochigi: 'utsunomiya', okinawa: 'naha')
# 3 pair['iwate': 'morioka'] pair['tochigi': 'utsunomiya'] pair['okinawa': 'naha']
arguments = (1, 2, iwate: 'morioka', tochigi: 'utsunomiya', 3, okinawa: 'naha')
def printArgs(args..):
list = args.elements
println(list)
hash = args.pairs
println(hash)
printArgs(arguments) # 1, 2, 3
# {'iwate': 'morioka', 'tochigi': 'utsunomiya', 'okinawa': 'naha'}
def func1(args.., foo): # 文法エラー: 可変長引数の後に通常の引数は持てない
pass
def func2(foo, lazy args..): # OK: 可変長引数かつlazyな引数である
pass
def func3(foo, ref args..): # コンパイルエラー: 可変長引数をrefで参照はできない(outも同様)
pass
def func4(args.., kwargs...): # 文法エラー: 可変長引数は最大1つのみ使用できます
pass
- コンテナを呼び出し側の関数の引数に
out
で展開する - コンテナの長さと引数の長さに注意(一致しない場合は実行時エラー)
- ペアオブジェクトは名前付き引数として処理される
- 名前付き引数に引数展開はできない
- 常に最後の引数にのみ使用でき、最大でも1つのみ使える
def prints(args..):
for a in args:
print(a.toString ~ ' ')
else:
println
def div(x, y):
println(x / y)
input = [5, 1.2, nil]
prints(out input) # 5 1.2 nil
prints(-5, 'foo', 3e3, out input) # -5 foo 3000 5 1.2 nil
prints(out 'bar') # b a r
div(out input) # 実行時エラー: 引数の数が一致しない
div(out input[0..2]) # 4.166666
div(out {'x': 10, 'y': 3}) # 3.333333
div(1, out {'y': 4}) # 0.25
- 引数が省略された場合の値を指定する
- 値は呼び出し側に展開されたものとして動作する(サンプル参照)
reflect(line)
が呼び出し側の行番号を取得できる所以- 宣言側で呼び出し側のスコープは参照できない
- オプション引数の後に通常の引数は使えない
def foo(x, y = 100, line = reflect(line)):
println("#{line}: #{x * y}")
foo(5) # 4: 500
# オプション引数は呼び出し側で展開されたものとなる
# そのため、上記呼び出しは以下と同じとなる
# foo(5, 100, reflect(line))
- 関数の呼び出し側で引数の名前を指定して呼び出せる
- ハッシュのように名前を指定するが、クォーテーションで括ってはいけない
- Pythonのような
=
での指定ではなく、C#と同様の:
- Pythonのような
- 名前付き引数であれば順序はどのようなものでもよい
def foo(x, y = 100, line = reflect(line)):
println("#{line}: #{x * y}")
foo(x:5, y: 4) # 4: 20
foo(2, y: 3) # 5: 6
foo(y: 1.5, 3) # 6: 4.5
- 通常の条件分岐
- 多項比較式が最も利用されると思われる
- 偽の場合、
elif
で再度条件分岐、最後にelse
で終わる
point = 82
if 0 <= point < 60:
rank = 'E'
elif 60 <= point < 70:
rank = 'D'
elif 70 <= point < 80:
rank = 'C'
elif 80 <= point < 90:
rank = 'B'
elif 90 <= point <= 100:
rank = 'A'
else:
println('incorrect!')
- if文の逆の処理を行う
elif
に当たる構文はない(そのような場合はif文を使う)
point = 82
unless 60 <= point <= 100:
println('bad')
else
println('good')
- 条件式が真の間は反復処理をする
do...while
に当たる構文はない(そのような場合は後置until文を使う)continue
で次のループに進める- 以下の反復文で同様に使える
- 後置反復文では使用不可
i = 0
while i++ < 5:
println('Hello!')
- 処理の後ろに置くwhile文
- whileの中が真だった場合のみ処理が実行される
i = 0
println('Hello!') while i++ < 5
- while文の逆の処理。条件式が真になるまで反復処理する
i = 0
until i++ == 5:
println('Hello!')
- 処理の後ろに置くuntil文
- untilの中が偽だった場合のみ処理が実行される
i = 0
println('Hello!') until i++ == 5
- (for文でも同じことができるためやや実用性に乏しいかも)
- 複数の処理を指定された回数だけ反復処理する
- 回数指定は式で指定可能
- それまでに何回反復したかの回数をとれる
times 5:
println('Hello!')
times new Random.nextInt(10, 5) for count:
println(count)
- 処理の後ろに置くtimes文
println('Hello!') times 5
println(count) times new Random.nextInt(10, 5) for count
- C言語のfor文とは全く異なる
- C# のforeach文に近い
- コンテナを列挙するための構文
for i in 0.countUp(10):
println(i) # 0 1 2 3 4 5 6 7 8 9 10
for value in [10, 20, 30]:
println(value / 10) # 1 2 3
for pair in {'Kawachi': '河内', 'Chiba': '千葉', 'Katsuragi': '葛城', 'Asaka': '浅香'}:
println("#{pair[0]}: #{pair[1]}")
- 処理の後ろに置くfor文
println(i) for i in 0.countUp(10) # 0 1 2 3 4 5 6 7 8 9 10
println(value / 10) for value in [10, 20, 30] # 1 2 3
- 反復文にはelse文をつけられる
- ループを抜けたときにelse文が実行される。ただし
break
、return
、throw
など、ループ早期脱出についてはelse文を実行しない - 後置文には付けられない
extended interface IIterable:
extended def all(input, selector):
for i in input:
return false unless selector(i)
else:
return true
def isPrime(n):
if n < 2 && n % 2 == 0:
return false
if n == 2:
return true
for i in n.stepTo(Math.sqrt(n).toInteger, 2):
if n % i == 0:
return false
else:
return true
- コルーチンを作る
yield [値]
によりジェネレータを作る
def foo:
print('The quick ')
yield
print('jumps over')
yield
print('dog.')
def bar:
print('brown fox ')
yield
print('the lazy ')
foo
bar
foo
bar
foo
# The quick brown fox jumps over the lazy dog.
extended class Integer:
extended def countUp(begin, end):
yield begin while begin++ < end
println(i) for i in 5.countUp(10)
- よくあるswitch文
- フォールスルーはできない。case文はラベルではない
- case文の中身を実行し終えたらswitch文を抜ける。gotoはできない(存在しない)
- breakはできない(不要)
default は case default とする- case文に入るのは式(予定)
- 評価のタイミングをどうする?
exitCode = 0
switch exitCode:
case 0:
println('成功')
case -1:
println('予期しないエラー')
default:
println('何らかのエラー')
- case文をカンマで区切って複数の値に対応させる
- case文を並べて書くことができないため
value = 0b01 | 0b10
switch value:
case 0b01, 0b11:
println('最下位ビットが1です')
case default:
println('最下位ビットは0です')
- case文に文字列が置ける
- 文字列が置けるので
nil
も置ける
protocol = 'http'
port = nil
switch protocol:
case 'http':
port = 80
case 'https':
port = 443
case default:
port = nil
- 関数の内部からオブジェクトを返す(戻り値)
- 多値を返す場合は
ref
またはout
、あるいはタプルを使う
- 多値を返す場合は
- 関数の内部のフローから早期脱出するためにも用いる
return
が書かれなかった関数の戻り値、return
単体でフローが返された場合はnil
が戻り値となる
- 例外処理、例外の捕捉に用いる
try
...catch
、try
...catch
...finally
、try
...finally
の順に用いるtry
文内でthrow
文により例外が送出されるとcatch
文で例外を補足できるcatch
文は捕捉したい例外の型をカンマ区切りで記述できる- 例外オブジェクトを取得したい場合は
catch [オブジェクト名] case [例外型名]:
の形式で可能 - 例外の型名を指定しない場合はすべての例外を捕捉できるが、それは最後のcatch文としなければならない
- 例外オブジェクトを取得したい場合は
finally
文は例外の発生、未発生に関わらずtry
文を脱出するときに必ず実行される文
def test(a, b):
try:
println("a // b = #{a} // #{b} = #{a // b}")
return a // b
catch ex case DivideByZeroException:
println('a または b がゼロです')
finally:
println('計算完了')
test(1, 2)
# 出力:
# a // b = 1 // 2 = 0
# 計算完了
test(1, 0)
# 出力:
# a または b がゼロです
# 計算完了
- 例外を送出する
- Exceptionクラスもしくはその派生クラスのみ送出できる
throw [例外オブジェクト]
とするとスタックトレースは送出した時点のものとなるthrow
単体はcatch
文の内部のみで使え、catch
文で捕捉した例外をそのまま送出する- これによりスタックトレースが元の場所のまま保持される
- 処理できない例外を上流に送出し直す場合に使う
def test(a):
if (test == nil)
throw new Exception('値を nil にすることはできません')
# 実際は契約とenforceを使ったやり方を推奨する
def test(a):
in:
enforce(test != nil, new Exception('値を nil にすることはできません'))
- 指定されたリソースオブジェクトを、with文を脱出した際に解放する
- 解放時にオブジェクトのdispose関数(引数なし)を呼び出す
- 解放時にdispose関数が呼び出せる状況ならどんなオブジェクトでもOK
- オブジェクトがIDisposableインタフェースを実装している必要はない(ダックタイピング)
- 脱出にはループ内での
break
またはcontinue
、return
、例外送出でも必ず実行される- これは
scope exit
文が実行されるのと同じ状況
- これは
with [変数名] = [初期化式]:
、厳密にはwith [式]:
のようにして記述する- 後者の書式では式で生成されたオブジェクトは名前で参照はできない場合があるが、必ず解放処理がなされる
- 複数のリソースに対しては
with [1つ目のリソース], [2つ目のリソース], ...
のように複数指定可能
class Resource(IDisposable):
def this:
println('Constructed!')
def dispose:
println('Disposed!')
while true:
with res = new Resource:
println('FooBar')
break
with obj = new Object:
obj.dispose = () => println('Pure object is disposed!')
# 出力:
# Constructed!
# FooBar
# Disposed!
# Pure object is disposed!
- スコープガード文
- D言語のスコープガード文と同じ。記述されたスコープを脱出する際に、文に記述された処理を実行する
- 複数のスコープガード文は 記述された順とは逆に 実行される
- フローがスコープガード文に到達しなかった場合はそのスコープガード文は実行されない
scope [キーワード]:
の書式で、指定できるのは以下の通りscope exit
: スコープから脱出する際に必ず実行されるscope success
: スコープから 例外送出以外で 脱出する際に必ず実行されるscope failure
: スコープから 例外送出で 脱出する際に必ず実行される
- その性質上、
scope success
節とscope failure
節は同時には決して実行されない - トップレベルスコープ(クラスや関数に属さないスコープ)でスコープガード文が使われると、それはプログラムの終了時に実行される
def test:
println('1')
scope exit:
println('2')
println('3')
scope success:
println('4')
println('5')
scope failure:
println('6')
return
println('7')
scope success:
println('8')
test
# 出力:
# 1
# 3
# 5
# 4
# 2
- 処理の後に置くif文のようなもの
- 他の後置文と違い、後置if式と後置unless式は式
return
などの文と使う場合は注意が必要
- 当てはまらない場合は
nil
を返し、else
句があるならばそちらの値を用いる elif
は使えないelse
句が無かった場合を無視すれば基本的に条件演算子? :
と役割は同じ- ただし多重に(入れ子として)使うと可読性は落ちるためそのような用法は推奨しない
- そういう場合は該当部分だけ関数化してif文判定したほうがマシです
- ただし多重に(入れ子として)使うと可読性は落ちるためそのような用法は推奨しない
input = 4.0
value = Math.sqrt(input) if input >= 0.0 # value = 2.0
value = -Math.sqrt(-input) if input < 0.0 # value = nil
value = Math.sqrt(input) if input >= 0.0 else -Math.sqrt(-input) # value = 2.0
# 上の式は下の条件演算子で書いても同じ結果
value = input >= 0.0 ? Math.sqrt(input) : -Math.sqrt(-input)
i = 0
v = i if i == 0 if i == 0 else i if i == 0 else i # 文法的に正しいが可読性に乏しい
v = (i if (i == 0 if i == 0 else (i if i == 0 else i))) # このように解釈される
v = i if i == 0 if i == 0 if i == 0 if i == 0 # これも同じ
v = (i if (i == 0 if (i == 0 if (i == 0 if i == 0)))) # 上もこれも文法的には正しいが...
def test(value):
return 'yep' if value < 2
return 'nope'
println(test(0)) # yep
println(test(1)) # yep
println(test(2)) # nil
# returnは文なので後置if式の影響を受けない
# よって return 'nope' は決して実行されない
- 後置if文の逆。偽の時に第二項の評価が行われる
extended interface IIterable:
extended def all(input, selector):
for i in input:
return false unless selector(i)
else:
return true
- Pythonと同じ、比較式をつなげて書ける
- 実際は
a < b < c
という式はa < b && b < c
と同じ a < b > c
のような数学的に綺麗ではない式も文法的に正しいとする- ただし、各項は最大一回のみ評価される
value = 72
if 0 <= value <= 100:
println('Correct value!')
else:
println('Incorrect value!')
- 名前が無く、引数リストと本体のみを指定して関数を書ける
- 一行で書ける処理に特化している。return は書けない(文は書けない。returnは式ではなく文)
- 一行で書けない場合は?何もしないラムダ式は? → def を使う
- 実行は関数呼び出しと同じ要領でできる
# 定数を生成するラムダ式
() => 1 + 1
# 引数をとるラムダ式
x => x + x
# 複数の引数をとるラムダ式
(x, y) => x * y
def dummy():
pass
# 返り値のないラムダ式
() => dummy
# 入れ子になったラムダ式
func = (x, y) => z => x + y == z
println(typeof(func)) # function
println(typeof(func(1, 2))) # function
println(typeof(func(1, 2)(3))) # boolean
- 代入演算子を繋げることで複数の変数に一括代入する
a = b = c = 'hey!'
print(a, b, c) # hey! hey! hey!
a = b = c = (3, 5)
print(a, b, c) # (3, 5) (3, 5) (3, 5)
- 列挙可能なものを展開して各変数に代入する
- 代入する値の数が代入先の変数の数未満だとコンパイルエラー
- 代入する値の数が代入先の変数の数より大きいと余った分は無視される
- マルチターゲット代入と併用可能
(a, b, c) = 'hey!'
print(a, b, c) # h e y
(a, b, c) = (3, 5) # コンパイルエラー
(a, b, c) = (1, 3, 5, 7)
print(a, b, c) # 1 3 5
a = (b, c) = (3, 5)
print(a, b, c) # (3, 5) 3 5
- 広い意味での契約
- 関数やクラスに対する表明、強制、例外事項を指定する。主に制約条件として呼び出し側/実装側に課す
- unittest(単体テスト)やinvariant(不変条件)はリリース実行時には実行されないが、in(事前条件)に記述された
exceptenforceはリリース時でも実行される- assertについては、
- それ以外の場所に書かれたものは警告を出す
- 無論、関数本体に書いても良いが、正しい使い方ではない
- 内部関数に書かれたものも警告を出す
- assertについては、
- assertとenforceはunittest、invariant、in/outに書かれるべきであり、成立しなければならない条件を書く
exceptはinに書かれるべきであり、成立してはいけない条件を書くenforceは例外のキャッチはできない。違反したら即座にプログラムを終了する- enforceは第一引数の結果が
nil
、また数値型の場合非ゼロ、ブール型の場合false
のとき例外を送出する。例外は第二引数に指定できる- 例外を送出しなかった時は第一引数の結果をそのまま返す
- 例外の詳細は 例外機構 にて
- それぞれの意義と使い方は以下のとおり
名称 | 意味 | Debug | Release | 説明 |
---|---|---|---|---|
assert | 表明 | ○ | × | 関数やクラスが正しく定義されるために、どんな場合も必ず成立する制約条件を書く |
enforce | 強制 | ○ | ||
例外 | ○ | ○ | (破棄) enforceと同じ意味になり、不要になりました。 |
class Size:
property x (get, set):
in:
enforce(x >= 0) # ユーザが負値を代入する恐れがある
property y (get, set):
in:
enforce(y >= 0) # 同様
def this(x = 0, y = 0):
this.x = x
this.y = y
def area():
out(result):
assert(result >= 0)
return this.x * this.y
unittest:
(x, y) = (3, 5)
size = new Size(x, y)
assert(size.area == x * y)
invariant:
assert(this.x >= 0)
assert(this.y >= 0)
#
unittest:
(x, y) = (3, 5)
size = new Size(x, y)
assert(size.x == x)
assert(size.y == y)
Assert.fail(size.x = -1)
Assert.fail(size.y = -1)
unittest:
Assert.fail(new Size(-1, 5))
Assert.fail(new Size(3, -1))
- D言語にある、コンテナをインデクスで範囲指定して切り出す機能
- 添字を整数の代わりに範囲リテラルで指定する
- コンテナのコピー、配列演算も可能。
$
がコンテナの長さのエイリアスになる。[]
がコンテナ全体を表すスライスになる。ただしコピー時はコピー元と先で要素数を一致させる必要あり - コンパイル時に操作が妥当なのかチェックが可能
a = list(0..10) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
b = new List(capacity: 10)
# a のコンテナ全体をコピー
b[] = a;
b[] = a[]
b[] = a[0..$]
c = a[]
# 0 から 5 までをコピー
b[0..5] = a[0..5]
# 2 から 8 までをコピー
b[0..6] = a[2..8]
# 同じ配列にコピー可能
a[0..4] = a[5..$]
# エラー。インデクスが境界の外
c = a[0..10];
# エラー。範囲が重複している
a[0..4] = a[2..6];
- D言語にある、配列などに配列演算を行えるもの
- ラムダ式と組み合わせて作用をさせる
a = list(0..10) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
b = new List(capacity: 10)
a[] += b[] + 5 # [5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
# 以下と動作は同じ
for i in 0..a.length:
a[i] += b[i] + 5;
a = list(0..10) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# ラムダ式と組み合わせて作用する
a[] = j => (j < 0 ? -j : j)
# 以下と動作は同じ
for i in 0..a.length:
j = array[i]
if j < 0:
array[i] = -j
else:
array[i] = j
- 破棄:
- 言語上重要機能ではないし、効果も少ない
- 強制的に並列計算するよう指示する
- かなりおまけ的で実験的で自己満足的機能。 通常は並列計算しても速くならないし、むしろ遅くなる場合がほとんど
- マニュアルにも使ってもいい事無いですよと記述しておく
- キーワードの 後に 配列計算が行われている場合のみキーワード化
parallel a[] += b[] + 5
- Pythonのジェネレータ式と機能は同じ
- 無名のジェネレータを生成することに等しい
- 丸括弧が必要。ただし関数の引数として指定する場合は省略可能
- リスト内包表記はできない
println(list(x ** 2 for x in 1..10)) # [1, 4, 9, 16, 25, 36, 49, 64, 81]
# 以下と同じ意味, 動作
def square_generator(source):
for x in source:
yield x ** 2
squares = list(square_generator(1..10))
println(squares)
- 複数のオブジェクトを格納できるコンテナオブジェクト
- インデックスアクセス可能、可変長、代入可能
- Luryは配列ではなくリストを使う(配列でできることはリストでできる)
- リストリテラルは
[ ]
を使う
- 複数のオブジェクトを格納できるコンテナオブジェクト
- インデックスアクセス可能、固定長、代入不可能
- ハッシュリテラルは
( )
を使う
- 複数のオブジェクトを、キーと値のペアで格納できるコンテナオブジェクト
- インデックスアクセス不可能(キーでアクセス)、可変長、代入可能
- ハッシュリテラルは
{ }
を使い、必ずキーと値のペアで記述する
- 複数のオブジェクトを格納できる、重複を許さないコンテナオブジェクト
- インデックスアクセス不可能、可変長、代入不可能
- ハッシュリテラルは
{ }
を使う(キーは指定しない)
- 空の状態を表す特殊な_記号_。他の言語の null や nil と同じ
typeof(nil)
は「オブジェクト (object)」を示す。型名ではないnil
はオブジェクトではないためtypeof
は使えない
nil
への代入はできない- 比較には
==
または!=
を用いる。真偽値型と可換でない(直接 条件式には使えない)
- 一般的な真偽値型(boolean)
- 関係演算子の値はすべて真偽値型になる。論理積、論理和の項は真偽値型である
true
が真、false
が偽- 一般的なブール演算は次の通り
A ・ B | !A | !B | A | B | A & B | A ^ B |
---|---|---|---|---|---|
False False | True | True | False | False | False |
True False | False | True | True | False | True |
False True | True | False | True | False | True |
True True | False | False | True | True | False |
- 2つの整数による範囲を指定できる。またはその範囲で整数を列挙するジェネレータである
- 演算子
..
を使う。書式は[begin]..[end]
のように使う- 範囲は begin, begin + 1, ..., end - 1 であり、つまり [begin, end) である
- 開始値
begin
および 終了値end
は整数オブジェクトでなくてはならない - 終了値
end
は省略可能。その場合、リテラルが意味するものは異なる- ジェネレータとして使う場合、
begin
から始まる無限のジェネレータとなる - 添え字として使う場合、
begin
から最終要素までの添字となる (list[begin..$]
と同じ)
- ジェネレータとして使う場合、
value = 5
value_is_correct = value in 0..10 # true
text = 'Have a nice day.'
println(text[4..]) # a nice day
- ダブルクォーテーション " で囲まれた文字列のみ、
#{...}
の形式で式を埋め込む
name = 'John'
"Hello #{name}!" # Hello John!
"1 + 1 = #{1 + 1}" # 1 + 1 = 2
a = -42
"|a| = #{a < 0 ? -a : a}" # |a| = 42
"|a| = #{Math.abs(a)}" # |a| = 42
"Message: #{'foo'}" # Message: foo
"Message: #{"bar"}" # 文法エラー。#{...}内で " は使えない
- D言語と同じくバッククオート ` で囲む。ダブルクオーテーション " も使える。
- 式の埋め込みにも対応する。
regex = `[@@][A-Za-z0-9_]+`
newLine = `(\n|\r\n|\r)`
message = `Twitterのアカウント名を抽出する場合は、以下の正規表現を使うとよいでしょう。
"#{regex}"
文字列に改行文字が入っているかを判定するには次の正規表現が使えます。
'#{newLine}'`
- D言語と同じ、実数型の拡張のようなもの
- 虚数型はサフィックス
i
がつく。複素数型は実数型と虚数型が何らかの形で加算されて表される- (どうでもいいが複素数型はリテラルではない)
1.0 # 実数
1.0i # 虚数
-2.5e100i # 虚数
0i # 虚数
1 + 3i # 複素数
5 * -2i # 虚数
1i * -1i # 虚数
(2.5 + 3i) * (2.5 - 3i) # 結果は実数だが複素数
i # これは識別子の i。
- D言語にある整数の任意の位置にアンダースコア
_
を挿入できるもの
10_000_000 # 10000000と同じ
1000_0000 # これも同じ
0b01101001 # = (105)10
0o755 # = (493)10
- 演算子の優先順位に依らず、必要な場合は基本的に左から1回のみ評価される
- 条件演算子のようなものはこの限りではない
def val(x):
print(x ~ ' ')
return x
foo = val(1) + val(2) * val(3) # output: 1 2 3
foo[val(1)] = val(2) + val(val(3) + val(4)) # output: 1 2 3 4 7
foo = val(1) if val(true) else val(2) # output: true 1
bar = 3
foo[val(bar++)] = foo[val(++bar)] # output: 3 5
- C# の null合体演算子と同じもの
- 左の項からチェックし、はじめに nil でないものを返す
- すべての項が nil ならば nil を返す
a = nil
b = 3
c = a ?? b # 3
class SingletonTest:
static var instance # nil
static def getInstance():
return instance ?? (instance = new SingletonTest)
private this():
pass
- 更新: typeofは演算子ではなく、組み込み関数として実装することにしました
- 指定されたオブジェクトの型情報を表すオブジェクトを取得する
- 取得されるのは型の情報を格納するTypeオブジェクト
- 型そのものの情報取得は
reflect
を使う
extended class Type:
extended def p(type):
println(type.name)
typeof(1).p # Integer
typeof(true).p # Boolean
typeof(1.5).p # Real
typeof('1').p # String
typeof([]).p # List
typeof(nil).p # 実行時エラー: nilに型はない
typeof(() => nil).p # Function
typeof(ref Type.p) # Function
typeof(typeof(1)).p # Type
class Foo:
def bar:
typeof(this).p # Foo
- 指定されたオブジェクトの実行時情報を取得する
- ただし現在のところ取得できるのは型、
関数、プロパティ、その他の特殊なもののみ- 型は型名を用いる。取得されるのは 型情報オブジェクト (Type)
関数オブジェクトについてはそのまま使える。無論、ラムダ式に対しても使える関数に対してはref
を使用した関数参照の形で用いる。この場合、関数の実行時情報が得られるプロパティに対してはref get
またはref set
を使用する- 関数およびプロパティに対して、関数オブジェクト(Function) を取り出したい場合は
(ref hoge).name
などで参照可能
- 関数およびプロパティに対して、関数オブジェクト(Function) を取り出したい場合は
- 以下のものが特殊なものです。以下はコンテキストキーワード化されます
- file: その記述がなされたファイルのフルパスを取得します。
eval
など、ファイル以外から実行されたものは特別な表記が用いられます。 - line: その記述がなされたファイル(または文字列)の物理行の番号を取得します。
- def: その記述がなされた関数の実行時情報(関数オブジェクト)を取得します。
- file: その記述がなされたファイルのフルパスを取得します。
型(クラス)に対する情報取得はtypeof
を使う
# reflect.lr
def func(x):
return y => x * y
# 関数名は暫定。ラムダ式やクロージャはユーザが参照できない関数名となる
println(reflect(file)) # .../reflect.lr
println(reflect(line)) # 6
println(reflect(ref func)) # Function: reflect.func
println(reflect(func(5))) # Function: reflect.__`rogue_func__`lambda0__`0
println(reflect(ref func(5))) # 文法エラー: 関数呼び出しにrefは使えません
println(reflect(x => x)) # Function: reflect.__`rogue__`lambda0__`0
println(reflect(line)) # 13
- (WIP) 優先順位の数字は後ほど整理
- 参考: - k3kaimu/d-manual/expr_operator.md - C# の式と文の一覧 (C#によるプログラミング入門)
優先順位 | 記号 | 名称 | 結合性 | 例 |
---|---|---|---|---|
1 | new | インスタンス生成 | n/a | new a |
1 | 型取得 | 左(→)? | typeof(a) | |
1 | nameof | 名前取得 | 左(→)? | nameof(a) |
1 | reflect | リフレクション | 左(→)? | nameof(a) |
1 | . | ドット演算子 | 左(→) | a.b |
1 | () | 関数呼び出し演算子 | 左(→)? | a(b) |
1 | [] | 配列演算子 | 左(→) | a[b] |
1 | .. | 範囲リテラル | n/a | a..b |
(1) | () | 括弧演算子 | 左(→) | (a) |
2 | ++ | 後置インクリメント | 右(←) | a++ |
2 | -- | 後置デクリメント | 右(←) | a-- |
3 | ++ | 前置インクリメント | 右(←) | ++a |
3 | -- | 前置デクリメント | 右(←) | --a |
3 | + | 符号非反転 | 右(←) | +a |
3 | - | 符号反転 | 右(←) | -a |
3 | ~ | ビット反転 | 右(←) | ~a |
3 | ! | 否定(NOT) | 右(←) | !a |
3 | not | 否定(NOT) | 右(←) | not a |
4 | ** | 冪乗 | 右(←) | a ** b |
5 | * | 乗算 | 左(→) | a * b |
5 | / | 除算 | 左(→) | a / b |
5 | % | 剰余算 | 左(→) | a % b |
5 | ~ | 配列結合 | 左(→) | a ~ b |
5 | // | 切り捨て除算 | 左(→) | a // b |
6 | + | 加算 | 左(→) | a + b |
6 | - | 減算 | 左(→) | a - b |
7 | << | 左シフト | 左(→) | a << b |
7 | >> | 右シフト | 左(→) | a >> b |
8 | == | 等価(同値性) | 左(→) | a == b |
8 | != | 非等価(同値性) | 左(→) | a != b |
8 | < | より小さい | 左(→) | a < b |
8 | > | より大きい | 左(→) | a > b |
8 | <= | 以下 | 左(→) | a <= b |
8 | >= | 以上 | 左(→) | a => b |
8 | is | 等価(同一性) | 左(→) | a is b |
8 | !is | 非等価(同一性) | 左(→) | a !is b |
8 | not is | 非等価(同一性) | 左(→) | a not is b |
8 | in | 配列に含まれる | 左(→) | a in b |
8 | !in | 配列に含まれない | 左(→) | a !in b |
8 | not in | 配列に含まれない | 左(→) | a not in b |
9.0 | & | 論理積(AND) | 左(→) | a & b |
9.1 | ^ | 排他的論理和(XOR) | 左(→) | a ^ b |
9.2 | | | 論理和(OR) | 左(→) | a | b |
10 | && | 論理積(短絡評価) | 左(→) | a && b |
10 | and | 論理積(短絡評価) | 左(→) | a and b |
11 | || | 論理和(短絡評価) | 左(→) | a || b |
11 | or | 論理和(短絡評価) | 左(→) | a or b |
12 | ? : | 条件演算子 | 右(←) | a ? b : c |
12 | if else | 後置if式 | 右(←) | b if a else c |
12 | unless else | 後置unless式 | 右(←) | b unless a else c |
13 | ?? | nil合体演算子 | 左(→) | a ?? b |
14 | => | ラムダ式 | 右(←) | ex.) a => b |
15 | = | 代入 | 右(←) | a = b |
15 | += | 加算代入 | 右(←) | a += b |
15 | -= | 減算代入 | 右(←) | a -= b |
15 | *= | 乗算代入 | 右(←) | a *= b |
15 | /= | 除算代入 | 右(←) | a /= b |
15 | %= | 剰余算代入 | 右(←) | a %= b |
15 | ~= | 配列結合代入 | 右(←) | a ~= b |
15 | **= | 冪乗代入 | 右(←) | a **= b |
15 | //= | 切り捨て除算代入 | 右(←) | a //= b |
15 | |= | 論理和代入 | 右(←) | a |
15 | &= | 論理積代入 | 右(←) | a &= b |
15 | ^= | 排他的論理和代入 | 右(←) | a ^= b |
15 | <<= | 左シフト代入 | 右(←) | a <<= b |
15 | >>= | 右シフト代入 | 右(←) | a >>= b |
16 | , | カンマ演算子 | 左(→) | a, b |
- 以下のトークンはキーワード(予約語)。識別子には使えません
a - el | en - i | l - prop | prot - thi | thr - y |
---|---|---|---|---|
abstract | enum | lazy | protected | throw |
and | extended | nameof | public | times |
break | false | new | ref | true |
case | finally | nil | reflect | try |
catch | for | not | return | unittest |
class | if | or | scope | unless |
continue | import | out | sealed | until |
def | in | override | static | var |
default | interface | pass | super | while |
elif | invariant | private | switch | with |
else | is | property | this | yield |
- 決まった文脈のときにキーワード化する。識別子にも使える
キーワード | コンテキスト |
---|---|
get | プロパティ内部 または ref get |
set | プロパティ内部 または ref set |
value | プロパティ内部 |
file | reflect(file) |
line | reflect(line) |
exit | scope exit: |
success | scope success: |
failure | scope failure: |
近日中に型システムの方針について書きます。しばらくお待ちください!