Skip to content

Instantly share code, notes, and snippets.

@nanase
Last active March 15, 2018 00:06
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save nanase/8d17c7baf30f2cacb4bf to your computer and use it in GitHub Desktop.
Save nanase/8d17c7baf30f2cacb4bf to your computer and use it in GitHub Desktop.
Lury 構想まとめ

Lury 構想まとめ

※ 項目の名前は適当

※ 実装してみたい全ての機能を記述しているわけではありません

※ サンプルコードは古い構想を含んでいるかもしれません。注意して読んでください

目次

この言語の目標

  1. 楽しい言語をつくろう

    • どんなに機能が充実していても楽しくなければ誰も使わない
  2. 読みやすい言語をつくろう

    • どんなに機能が充実していてもわかりにくければ誰も学ばない
  3. 書きやすい言語をつくろう

    • どんなに機能が充実していてもドキュメントがなければ誰も使えない

楽しいプログラム ≠ 読みやすいプログラム ≠ 書きやすいプログラム

「メモ帳でも書きやすい」は比喩表現じゃない

言語機能

モジュール

  • クラス、インタフェース、クラスに所属しない関数を格納するための集合
  • 上記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

改名import

  • 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では通常、殆どのクラスの要素は外部のクラスからアクセスできる
  • アクセスを禁止したい場合はアクセス修飾子をつけ、隠蔽する
  • アクセシビリティは 何も指定しないpublicprotectedprivate の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
  • スーパークラスを指し示すキーワード
  • 関数のように呼び出すと、_呼び出した関数と同じ名前を持つスーパークラスの関数_を呼び出す
    • これによりオーバーライドした関数は単に 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 キーワードが付けられたメンバは、そのサブクラスでオーバーライドできない
  • 無論、overridesealedを併用できる(順番は無関係)
  • 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 とアクセシビリティを記述することもできない
  • 契約は書けるが、実装本体は書けない
    • ただし不変条件は書けません
  • インタフェースはインスタンス化できない
  • クラスがインタフェースを「実装」する。クラスはインタフェースをいくつでも実装できる
  • インタフェースは必要でない。ダックグタイピングによって同名の関数やプロパティにはアクセスできる
    • インタフェースは契約を利用するため、および is による実装判定のために用いる部分が大きい
  • インタフェースはインタフェースを継承できる
    • インタフェースAを継承するインタフェースBを実装する場合、Bを実装する記述をすればAも実装することとなる。また、この場合は A, B と記述しても良い
  • インタフェースの命名に規則性はない(が、クラスとの混同を避けるためにプレフィクス 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)の精度は持つ
実数型
  • RealFloat型の具象クラスとしての存在
複素数型
  • 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式を自動生成する
    • プロパティに対しては getset の直後に : が、どちらも同じ場合に限り -> で指定可能。両者の併用はコンパイルエラー
    • コンストラクタの返り値には指定不可
  • リリース実行時には型アノテーションのチェックは行われない
	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コンストラクタ

  • クラスのコンストラクタはthisを使う
    • 実はC#は委譲コンストラクタで自クラスのコンストラクタを呼び出すのにthisを既に使ってたりする
	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

  • 単体テストを記述できる。言語が単体テストの構文を用意する
  • 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')

ref/out

  • 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

lazy

  • いわゆる遅延評価。利用側から見ると遅延評価されているように見える
  • 関数側から見ると、利用側が渡した引数の値を生成する関数の形で受け取られる
  • 単なる関数参照なので返り値のない関数も渡せる
	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#と同様の :
  • 名前付き引数であれば順序はどのようなものでもよい
	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

if文

  • 通常の条件分岐
  • 多項比較式が最も利用されると思われる
  • 偽の場合、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!')

unless文

  • if文の逆の処理を行う
  • elifに当たる構文はない(そのような場合はif文を使う)
	point = 82

	unless 60 <= point <= 100:
		println('bad')
	else
		println('good')

while文

  • 条件式が真の間は反復処理をする
  • do...whileに当たる構文はない(そのような場合は後置until文を使う)
  • continueで次のループに進める
    • 以下の反復文で同様に使える
    • 後置反復文では使用不可
	i = 0

	while i++ < 5:
		println('Hello!')

後置while文

  • 処理の後ろに置くwhile文
  • whileの中が真だった場合のみ処理が実行される
	i = 0

	println('Hello!') while i++ < 5

until文

  • while文の逆の処理。条件式が真になるまで反復処理する
	i = 0

	until i++ == 5:
		println('Hello!')

後置until文

  • 処理の後ろに置くuntil文
  • untilの中が偽だった場合のみ処理が実行される
	i = 0

	println('Hello!') until i++ == 5

times文

  • (for文でも同じことができるためやや実用性に乏しいかも)
  • 複数の処理を指定された回数だけ反復処理する
  • 回数指定は式で指定可能
  • それまでに何回反復したかの回数をとれる
	times 5:
		println('Hello!')

	times new Random.nextInt(10, 5) for count:
		println(count)

後置times文

  • 処理の後ろに置くtimes文
	println('Hello!') times 5

	println(count) times new Random.nextInt(10, 5) for count

for文

  • 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文

  • 処理の後ろに置く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文をつけられる
  • ループを抜けたときにelse文が実行される。ただしbreakreturnthrowなど、ループ早期脱出については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文

  • コルーチンを作る
  • 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文

  • よくあるswitch文
  • フォールスルーはできない。case文はラベルではない
  • case文の中身を実行し終えたらswitch文を抜ける。gotoはできない(存在しない)
  • breakはできない(不要)
  • default は case default とする
  • case文に入るのは式(予定)
    • 評価のタイミングをどうする?
	exitCode = 0

	switch exitCode:
		case 0:
			println('成功')

		case -1:
			println('予期しないエラー')

		default:
			println('何らかのエラー')

switch文のカンマ区切りcase

  • case文をカンマで区切って複数の値に対応させる
  • case文を並べて書くことができないため
	value = 0b01 | 0b10

	switch value:
		case 0b01, 0b11:
			println('最下位ビットが1です')

		case default:
			println('最下位ビットは0です')

文字列比較switch文

  • case文に文字列が置ける
  • 文字列が置けるので nil も置ける
	protocol = 'http'
	port = nil

	switch protocol:
		case 'http':
			port = 80

		case 'https':
			port = 443

		case default:
			port = nil

return文

  • 関数の内部からオブジェクトを返す(戻り値)
    • 多値を返す場合はrefまたはout、あるいはタプルを使う
  • 関数の内部のフローから早期脱出するためにも用いる
  • returnが書かれなかった関数の戻り値、return単体でフローが返された場合はnilが戻り値となる

try文

  • 例外処理、例外の捕捉に用いる
  • try...catchtry...catch...finallytry...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 がゼロです
	# 計算完了

throw文

  • 例外を送出する
  • 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文

  • 指定されたリソースオブジェクトを、with文を脱出した際に解放する
  • 解放時にオブジェクトのdispose関数(引数なし)を呼び出す
    • 解放時にdispose関数が呼び出せる状況ならどんなオブジェクトでもOK
    • オブジェクトがIDisposableインタフェースを実装している必要はない(ダックタイピング)
  • 脱出にはループ内でのbreakまたはcontinuereturn、例外送出でも必ず実行される
    • これは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!

scope文

  • スコープガード文
  • 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文のようなもの
  • 他の後置文と違い、後置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' は決して実行されない

後置unless式

  • 後置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

assert/enforce/except

  • 広い意味での契約
  • 関数やクラスに対する表明、強制、例外事項を指定する。主に制約条件として呼び出し側/実装側に課す
  • unittest(単体テスト)やinvariant(不変条件)はリリース実行時には実行されないが、in(事前条件)に記述されたexcept enforceはリリース時でも実行される
    • assertについては、
      • それ以外の場所に書かれたものは警告を出す
      • 無論、関数本体に書いても良いが、正しい使い方ではない
      • 内部関数に書かれたものも警告を出す
  • assertとenforceはunittest、invariant、in/outに書かれるべきであり、成立しなければならない条件を書く
  • exceptはinに書かれるべきであり、成立してはいけない条件を書く
  • enforceは例外のキャッチはできない。違反したら即座にプログラムを終了する
  • enforceは第一引数の結果が nil、また数値型の場合非ゼロ、ブール型の場合 false のとき例外を送出する。例外は第二引数に指定できる
    • 例外を送出しなかった時は第一引数の結果をそのまま返す
    • 例外の詳細は 例外機構 にて
  • それぞれの意義と使い方は以下のとおり
名称 意味 Debug Release 説明
assert 表明 × 関数やクラスが正しく定義されるために、どんな場合も必ず成立する制約条件を書く
enforce 強制 × assertに加え、成立していなければプログラムを強制終了するような強い制約条件を書く。 入力として想定できるが、処理の進行に支障をきたす条件を書く
except 例外 (破棄) 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キーワード

  • 破棄:
    • 言語上重要機能ではないし、効果も少ない
  • 強制的に並列計算するよう指示する
  • かなりおまけ的で実験的で自己満足的機能。 通常は並列計算しても速くならないし、むしろ遅くなる場合がほとんど
  • マニュアルにも使ってもいい事無いですよと記述しておく
  • キーワードの 後に 配列計算が行われている場合のみキーワード化
	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は配列ではなくリストを使う(配列でできることはリストでできる)
  • リストリテラルは [ ] を使う

タプル

  • 複数のオブジェクトを格納できるコンテナオブジェクト
  • インデックスアクセス可能、固定長、代入不可能
  • ハッシュリテラルは ( ) を使う

ハッシュ

  • 複数のオブジェクトを、キーと値のペアで格納できるコンテナオブジェクト
  • インデックスアクセス不可能(キーでアクセス)、可変長、代入可能
  • ハッシュリテラルは { } を使い、必ずキーと値のペアで記述する

セット

  • 複数のオブジェクトを格納できる、重複を許さないコンテナオブジェクト
  • インデックスアクセス不可能、可変長、代入不可能
  • ハッシュリテラルは { } を使う(キーは指定しない)

nil

  • 空の状態を表す特殊な_記号_。他の言語の null や nil と同じ
  • typeof(nil)は「オブジェクト (object)」を示す。型名ではない
    • nilはオブジェクトではないためtypeofは使えない
  • nilへの代入はできない
  • 比較には == または != を用いる。真偽値型と可換でない(直接 条件式には使えない)

true/false

  • 一般的な真偽値型(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   # これも同じ

2進、8進整数

	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

nil合体演算子

  • 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

  • 更新: 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

reflect

  • 指定されたオブジェクトの実行時情報を取得する
  • ただし現在のところ取得できるのは型、関数、プロパティ、 その他の特殊なもののみ
    • 型は型名を用いる。取得されるのは 型情報オブジェクト (Type)
    • 関数オブジェクトについてはそのまま使える。無論、ラムダ式に対しても使える
    • 関数に対しては ref を使用した関数参照の形で用いる。この場合、関数の実行時情報が得られる
    • プロパティに対しては ref get または ref set を使用する
      • 関数およびプロパティに対して、関数オブジェクト(Function) を取り出したい場合は (ref hoge).name などで参照可能
    • 以下のものが特殊なものです。以下はコンテキストキーワード化されます
      • file: その記述がなされたファイルのフルパスを取得します。evalなど、ファイル以外から実行されたものは特別な表記が用いられます。
      • line: その記述がなされたファイル(または文字列)の物理行の番号を取得します。
      • def: その記述がなされた関数の実行時情報(関数オブジェクト)を取得します。
  • 型(クラス)に対する情報取得は 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

演算子一覧

優先順位 記号 名称 結合性
1 new インスタンス生成 n/a new a
1 typeof 型取得 左(→)? 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:
@HaiTo
Copy link

HaiTo commented Nov 29, 2014

型システムの導入は?

@nanase
Copy link
Author

nanase commented Nov 29, 2014

近日中に型システムの方針について書きます。しばらくお待ちください!

@HaiTo
Copy link

HaiTo commented Dec 5, 2014

例外アドベントカレンダー見てて、そういえば例外機構がないことを思い出しました

@nanase
Copy link
Author

nanase commented Dec 6, 2014

これから書きます!
例外送出はexceptになりそうです。

@HaiTo
Copy link

HaiTo commented Dec 7, 2014

👍 楽しみです! 

@HaiTo
Copy link

HaiTo commented Dec 9, 2014

経緯とかわかってないんですが、なぜシフト演算子があるのでしょうか?(ここでいうシフト演算子は、ビットのシフトだと認識しております。違ったらごめんなさい)
メモリ空間(バイナリ領域)への(間接的な)アクセスはポインターへのアクセスが出来ない(ように見える)このLuryでは取り扱わないほうが良いのではないでしょうか? 
もちろんPythonやC#では実装されていますが、個人的には(個人的には、大切なことなので二度(ry)、不要かなーと思いました。

@HaiTo
Copy link

HaiTo commented Dec 9, 2014

と加筆修正まで行ってからですが、科学計算等で用いられる可能性があるわけですね……
失礼しました 🙇

@nanase
Copy link
Author

nanase commented Dec 10, 2014

仰るとおり、科学計算などでシフト演算はよく使われます。
昔のプログラムの使用という意味で使われることも多そうですが...(参考)

ビット演算(!^&| )を導入するにあたって、シフト演算は必要不可欠と思い導入することにしてみました。
ただし、符号付き右シフト(>>>)は果たして必要なのかという疑問も生じてるので今後これについては消える可能性が高いです。(整数型の内部実装に依存してしまう→状況によって結果が変わってしまうという理由もあり)

また、シフト演算と除算は一般的に可換ではありません。シフト演算は整数に対する演算なので、実数に対して使うと言語と実装によっては結果が変わります。
これを期待してシフト演算を利用しているプログラマもいると思われます(私は座標計算で使ったり)

/* [JavaScript] */
0.0 / 16     // 0.0
0.0 >> 4     // 0

-0.0 / 16    // -0.0
-0.0 >> 4    // 0

@HaiTo
Copy link

HaiTo commented Dec 11, 2014

あ〜 座標計算確かにありそうですね。
ありがとうございます 👍

@HaiTo
Copy link

HaiTo commented Dec 12, 2014

https://gist.github.com/nanase/8d17c7baf30f2cacb4bf#プロパティ
こちらですが、「使えない」というよりは、User定義かコンパイラ(インタープリタ?)が、そのどちらかをオーバーライドしてしまう、という仕様のほうがはっきりするかなーと思いました。如何でしょう?
もしくは、User定義するとコンパイルエラー?

@nanase
Copy link
Author

nanase commented Dec 12, 2014

今のところはコンパイルエラーで考えています。(呼び出し側で区別できない)
ただしプロパティ名と同じ名前を持つ関数であっても引数で区別可能なら(=シグネチャが異なるなら)オーバーロード可能と考えています。

@HaiTo
Copy link

HaiTo commented Dec 12, 2014

関数であっても引数で区別可能なら

Rubyいじりすぎててこれを忘れていました。確かにそれで出来そうですね。 👍

@HaiTo
Copy link

HaiTo commented Dec 19, 2014

いつの間にか契約に enforce なるものが追加されていますが、これを assert と区別する理由はあるのでしょうか?

@nanase
Copy link
Author

nanase commented Dec 20, 2014

D言語のenforceの動作を誤解していました。
動作を再検討したところ、enforceの動作を変更/増強し、exceptを破棄することとしました。
assertenforceは契約に失敗すると例外を送出するところは同じですが、リリース実行時(コマンドライン引数で指定)にはassertの評価はされません。enforceはリリース実行時でも評価されます。

exceptでできていたことはenforceで完全にできるようになったので破棄しました。

@HaiTo
Copy link

HaiTo commented Jan 6, 2015

https://gist.github.com/nanase/8d17c7baf30f2cacb4bf#型アノテーション
引数リスト とは一体……?

実装が見えてきましたね!

@nanase
Copy link
Author

nanase commented Jan 6, 2015

引数リストとは関数の引数の列挙のことです。
以下の例で関数 foo の引数リストは (val1, ref val2, val3) です。

def foo(val1, ref val2, val3):
  pass

引数リストの後に -> をつけることで関数の返り値に対する型アノテーションを付けたいと思っています(Python 3と同じ)。これを : とすることは文法上できませんが、何か他に良い記号があれば採用したいと思っています。

追記: => は既にラムダ式で使われているため、文法上曖昧となり使用できません。

@HaiTo
Copy link

HaiTo commented Jan 19, 2015

def foo(val1, ref val2, val3)->Integer
  # ?

def foo(val1, ref val2, val3):->Interger
  #?

def foo(val1, ref val2, val3)->Integer:
  #?

イメージとしては 3番目 なのですが、どんな感じでしょ?

@nanase
Copy link
Author

nanase commented Jan 19, 2015

考えた結果、上記のまま(3番目)のままにしようと思います。
Python3と同じ文法ですが、逆に同じであるため混乱が少ないと思われます。

@giruzou
Copy link

giruzou commented Mar 4, 2018

luryっていい名前ですね!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment