機能検討 - 型推論・契約プログラミング

まだ計画段階。[[未実装]]

変数が型を持つ: 静的な型づけ

Ruby はやたらめったら動的に (実行時に) 決まるので, エラーチェックが難しい。

特に, オブジェクトには型があるのに, 変数に型がないため, メソッド定義の冒頭でだいたい型チェックが必要。

Python は, Python 3.5 から型ヒントが導入された。Python 3.10 から union type を int | str のように書けるようになるなど、順次、強化されている。

Ruby のほうは, Ruby 3.0 で RBS という別ファイルに型情報を準備するようになった。しかし正直、これはダメだ。(1) 別ファイルというコンセプトが間違っている。(2) メソッドだけやん。

契約プログラミング

メソッドの引数の型チェック

メソッドの引数の検査は, 品質を確保するには、最低限必要。繰り返し繰り返し同じ話題が出てくる。

  • flint blog: Ruby でも型チェック お手製の check_type() を作り、各メソッドの冒頭でチェックする。
  • ヽ( ・∀・)ノくまくまー(2010-04-26) [リンク切れ]

関数だけでなく、λ式の引数も同様に検査ができるようになるとよい。

型検査ツール

関数内での assert だけでは引数のチェックしかしがたい。戻り値の型もチェックしたい。

Ruby の 型検査ツールとして Sorbet がある; Sorbet · A static type checker for Ruby 若干冗長だが、これはよい。

Ruby
[RAW]
  1. class A
  2. extend T::Sig
  3. sig {params(x: Integer).returns(String)}
  4. def bar(x)
  5. x.to_s
  6. end
  7. end
Ruby
[RAW]
  1. sig {returns(T.proc.params(a1: Integer).returns(Integer))}
  2. def lowercase_proc
  3. # if you are using `proc`, use `T.proc` in your sig
  4. # this is the preferred approach
  5. proc {|n| n * 2 }
  6. end

Crystal のように関数宣言やλ式の仮引数に型を書くのがシンプルでよい。shiika-lang/shiika: A statically-typed programming language も同じコンセプトで、Ruby に型が付いている。基本はこの線がいい。あとは、型を必須にするかどうか。minilang は, サブセットにするため、型は必須にしない。

上記のような型宣言だけ別にするのもアリはアリ。とはいえ、一段劣る。

Python 3 は次のようなおもしろ宣言になる。(変数のほうに型を付けるため)

from typing import Callable
func: Callable[[str, str], int] = lambda var1, var2: var1.index(var2)

契約プログラミング

引数と戻り値の検査は、型に限定せず一般化すると、契約プログラミングになる。

Design by Contract は, プログラミング言語 Eiffel が導入した概念 -- Design by Contract - Eiffel Software - The Home of EiffelStudio. とはいえ, Eiffel はまったく誰も使っていない, 完全に廃れたプログラミング言語。

Ruby用語集 によれば,

Eiffel
オブジェクト指向プログラミング言語。matzは昔この言語の作者の本 (Object-oriented Software Construction 邦訳「オブジェクト指向入門」) を読んで目から鱗が落ちたらしい。その割にはRubyは Eiffelに似ていない。似ているのはブロックが end で終るところと、rescue という予約語だけか。

残念なことに, 契約プログラミングのような重要な概念は, 目に入らなかったらしい。

それはともかく, 契約プログラミングが実装されたのは, .NET Framework 4 (ただし Visual Studio 2015まで. .NET Core ではサポートされない), D 言語ぐらい。

次のページで, D言語の契約プログラミングが分かる; 契約プログラミング - D言語ツアー. in ブロックと out ブロックで, 入力パラメータと関数の戻り値をチェックできる。見た感じ、普通の文が書けるようだ。

メソッドの前後に呼び出されるという意味では, Common Lisp の :before, :after メソッドを使ってもよい。Quid Pro Quo という契約プログラミングのライブラリもある。

Common Lisp は補助メソッドを別に定義するスタイル。これでもよい。

C++ も, C++20 に契約プログラミングを導入しようとして、今では C++26 を目指して開発が継続している。しかし、まだ記法も複数の提案がある状況。間に合うのかな? [C++] WG21月次提案文書を眺める(2023年01月) - 地面を見下ろす少年の足蹴にされる私

C++ の契約機能は, 安全第一にするため、契約条件における副作用は定数式における関数実行と同じ制限が掛かる見込み。これはコンパイル時計算の機能がないと実装が難しい。

メソッド引数以外, 型推論

メソッドの引数に型を強制しない。型推論が必要。

ローカル変数、インスタンス変数、クラス変数の型推論も必要。Ruby では, 次のように分岐による判定が多用される。これはカバーしたい。

Ruby
[RAW]
  1. case a
  2. when A then p "class A"
  3. when B then p "class B"
  4. end

そのためには, Ruby ではメソッドになっている Object#is_a?(mod) (サブクラス含む), Object#instance_of?(klass) 単に削除がよい, Object#nil? を予約語化するのがよい。

Object#respond_to? (単数形) と Object#class も.

new() の再定義を禁止しないといけない。