RBS、Rubysの新しいタイプ注釈システムを理解する
RBSは、Rubyの新しい型構文フォーマット言語の名前です。 RBSを使用すると、 .rbsという新しい拡張子を持つファイルのRubyコードに型注釈を追加できます。 。次のようになります:
class MyClass
def my_method : (my_param: String) -> String
end
RBSで型注釈を提供することにより、次のような利点が得られます。
- コードベースの構造を定義するためのクリーンで簡潔な方法。
- クラスを直接変更するのではなく、ファイルを介してレガシーコードに型を追加するより安全な方法。
- 静的および動的タイプチェッカーと普遍的に統合できる可能性。
- メソッドのオーバーロード、ダックタイピング、動的インターフェースなどを処理するための新機能。
ちょっと待って! SorbetやSteepのような静的なタイプチェッカーはすでにありませんか?
はい、そして彼らは素晴らしいです!しかし、4年間の議論とコミュニティで構築された少数のタイプチェッカーの後、Rubyコミッターチームは、タイプチェッカーツールを構築するためのいくつかの標準を定義する時が来たと考えました。
RBSは正式には言語であり、Ruby3とともに登場します。
さらに、Rubyの動的型付けの性質、およびダックタイピングやメソッドのオーバーロードなどの一般的なパターンのために、可能な限り最善のアプローチを確保するためにいくつかの予防措置が講じられています。これについては、後ほど詳しく説明します。
Ruby3インストール
ここに示す例に従うには、Ruby3をインストールする必要があります。
多くのRubyバージョンを管理する必要がある場合は、公式の指示に従うか、ruby-buildを介してそれを行うことができます。
または、 rbs
をインストールすることもできます 現在のプロジェクトに直接宝石を入れる:
gem install rbs
先に進む前に、この概念を明確にしましょう。動的に型付けされた言語は静的な言語とどのように比較されますか?
RubyやJavaScriptなどの動的型付けされた言語では、実行時に禁止された操作が発生した場合の処理方法をインタープリターが理解するための事前定義されたデータ型はありません。
これは、静的型付けから期待されるものの反対です。 JavaやCなどの静的に型付けされた言語は、コンパイル時に型を検証します。
次のJavaコードスニペットを参照してください:
int number = 0;
number = "Hi, number!";
2行目でエラーがスローされるため、静的型付けではこれは不可能です。
error: incompatible types: String cannot be converted to int
次に、Rubyで同じ例を取り上げます。
number = 0;
number = "Hi, number!";
puts number // Successfully prints "Hi, number!"
Rubyでは、変数のタイプはその場で変化します。つまり、インタプリタは、ある変数から別の変数に動的にスワップする方法を知っています。
その概念は一般的に強く型付けされたと混同されます vs弱いタイプ 言語。
Rubyは動的であるだけでなく、強く型付けされた言語でもあります。つまり、実行時に変数がその型を変更できるようにします。ただし、クレイジーなタイプのミキシング操作を実行することはできません。
前の例を応用した次の例(Ruby)を見てください:
number = 2;
sum = "2" + 2;
puts sum
今回は、異なるタイプ( Integer
)に属する2つの数値の合計を試しています。 およびString
)。 Rubyは次のエラーをスローします:
main.rb:2:in `+': no implicit conversion of Integer into String (TypeError)
from main.rb:2:in `<main>'
言い換えれば、Rubyは、(おそらく)異なるタイプを含む複雑な計算を実行する仕事はすべてあなた次第だと言っています。
一方、JavaScriptは、およびが弱いです。 動的に型付けされ、同じコードで異なる結果が得られます:
> number = 2
sum = "2" + 2
console.log(sum)
> 22 // it concatenates both values
RBSはSorbetとどのように異なりますか?
まず、それぞれが採用するアプローチからコードへの注釈付けまで。 Sorbetはコード全体に注釈を明示的に追加することで機能しますが、RBSでは .rbsを使用して新しいファイルを作成する必要があります。 拡張機能。
これの主な利点は、レガシーコードベースの移行について考えるときです。元のファイルは影響を受けないため、プロジェクトにRBSファイルを採用する方がはるかに安全です。
作成者によると、RBSの主な目標は構造を説明することです。 あなたのRubyプログラムの。クラス/メソッドの署名のみを定義することに焦点を当てています。
RBS自体はタイプチェックできません。その目標は、タイプチェッカー(SorbetやSteepなど)が仕事をするための基礎として構造を定義することに限定されています。
単純なRuby継承の例を見てみましょう:
class Badger
def initialize(brand)
@brand = brand
end
def brand?
@brand
end
end
class Honey < Badger
def initialize(brand: "Honeybadger", sweet: true)
super(brand)
@sweet = sweet
end
def sweet?
@sweet
end
end
すごい!いくつかの属性と推測されたタイプを持つ2つのクラス。
以下に、可能なRBS表現を示します。
class Brand
attr_reader brand : String
def initialize : (brand: String) -> void
end
class Honey < Brand
@sweet : bool
def initialize : (brand: String, ?sweet: bool) -> void
def sweet? : () -> bool
end
かなり似ていますね。ここでの主な違いはタイプです。 初期化コード>
Honey
のメソッド たとえば、クラスは1つの String
を受け取ります および1つのboolean
パラメータを設定し、何も返しません。
一方、Sorbetチームは、RBI(Sorbetの型定義のデフォルト拡張)とRBSの間の相互運用性を可能にするツールの作成に緊密に取り組んでいます。
目標は、RBSの型定義ファイルの使用方法を理解するためにSorbetと任意の型チェッカーの基礎を築くことです。
言語を使い始めて、すでにいくつかのプロジェクトが進行中の場合、どこでどのように入力を開始するかを推測するのは難しいかもしれません。
これを念頭に置いて、Rubyチームは rbs
と呼ばれる非常に便利なCLIツールを提供してくれました。 既存のクラスのスキャフォールドタイプに。
使用可能なコマンドを一覧表示するには、 rbs help
と入力するだけです。 コンソールで結果を確認します:
CLIツールで使用可能なコマンド。
おそらく、リストで最も重要なコマンドは protocol
です パラメータとして提供されたソースコードファイルのASTを分析して、「おおよその」RBSコードを生成するためです。
100%効果がないので概算。レガシーコードは主に型指定されていないため、そのスキャフォールドコンテンツのほとんどは同じ方法で提供されます。たとえば、明示的な割り当てがない場合、RBSは一部のタイプを推測できません。
別の例を参考にしてみましょう。今回は、カスケード継承に3つの異なるクラスが含まれます。
class Animal
def initialize(weight)
@weight = weight
end
def breathe
puts "Inhale/Exhale"
end
end
class Mammal < Animal
def initialize(weight, is_terrestrial)
super(weight)
@is_terrestrial = is_terrestrial
end
def nurse
puts "I'm breastfeeding"
end
end
class Cat < Mammal
def initialize(weight, n_of_lives, is_terrestrial: true)
super(weight, is_terrestrial)
@n_of_lives = n_of_lives
end
def speak
puts "Meow"
end
end
属性とメソッドを持つ単純なクラス。それらの1つにはデフォルトのブール値が提供されていることに注意してください。これは、RBSが単独で推測するときに何ができるかを示すために重要です。
次に、これらのタイプをスキャフォールディングするために、次のコマンドを実行してみましょう。
rbs prototype rb animal.rb mammal.rb cat.rb
必要な数のRubyファイルを渡すことができます。この実行の結果は次のとおりです。
class Animal
def initialize: (untyped weight) -> untyped
def breathe: () -> untyped
end
class Mammal < Animal
def initialize: (untyped weight, untyped is_terrestrial) -> untyped
def nurse: () -> untyped
end
class Cat < Mammal
def initialize: (untyped weight, untyped n_of_lives, ?is_terrestrial: bool is_terrestrial) -> untyped
def speak: () -> untyped
end
予測したように、RBSは、クラスを作成したときに目指していたタイプのほとんどを理解できません。
ほとんどの仕事は、 untyped
を手動で変更することです。 本物への参照。これを達成するためのより良い方法を見つけることを目的としたいくつかの議論が現在コミュニティで行われています。
メタプログラミングに関しては、 rbs
ツールは動的な性質があるため、あまり役に立ちません。
例として次のクラスを取り上げます。
class Meta
define_method :greeting, -> { puts 'Hi there!' }
end
Meta.new.greeting
このタイプの足場の結果は次のようになります:
class Meta
end
Rubyは、オブジェクトの性質(オブジェクトのタイプ)についてはあまり気にしませんが、オブジェクトの機能(オブジェクトの機能)については気にします。
ダックタイピングは、モットーに従って動作する有名なプログラミングスタイルです:
「オブジェクトがアヒルのように動作する場合(話す、歩く、飛ぶなど)、それはアヒルです」
言い換えれば、Rubyは、元の定義とタイプがアヒルを表すことを想定していなくても、常にアヒルのように扱います。
ただし、ダックタイピングはコード実装の詳細を隠す可能性があり、簡単にトリッキーになり、見つけたり読んだりするのが難しくなる可能性があります。
RBSはインターフェースタイプの概念を導入しました 、具体的なクラスやモジュールに依存しないメソッドのセットです。
前の動物の継承の例を取り上げて、 Cat
からの陸生動物の新しい階層レベルを追加するとします。 継承します:
class Terrestrial < Animal
def initialize(weight)
super(weight)
end
def run
puts "Running..."
end
end
地上以外の子オブジェクトが実行されないようにするために、このようなアクションの特定のタイプをチェックするインターフェイスを作成できます。
interface _CanRun
# Requires `<<` operator which accepts `Terrestrial` object.
def <<: (Terrestrial) -> void
end
RBSコードを特定の実行メソッドにマッピングする場合、それが署名になります:
def run: (_CanRun) -> void
誰かがTerrestrialオブジェクト以外のものをメソッドに渡そうとすると、タイプチェッカーは必ずエラーをログに記録します。
また、Rubyistの間では、さまざまなタイプの値を保持する式を使用することもよくあります。
def fly: () -> (Mammal | Bird | Insect)
RBSは、パイプ em>を介して結合するだけで、共用体タイプに対応します。 オペレーター。
(実際には多くのプログラミング言語の中で)別の一般的な方法は、メソッドのオーバーロードを許可することです。この場合、クラスは同じ名前の複数のメソッドを持つことができますが、シグネチャは異なります(パラメータのタイプや数、順序など)。 。
動物が最も近い進化のいとこを返すことができる例を見てみましょう:
def evolutionary_cousins: () -> Enumerator[Animal, void] | { (Animal) -> void } -> void
このようにして、RBSを使用すると、特定の動物が単一の進化のいとこを持っているのか、それともそれらの束を持っているのかを明示的に判断できます。
TypeProf
並行して、Rubyチームはtypeprofと呼ばれる新しいプロジェクトも開始しました。これは、RBSコンテンツの分析と(試行)を目的とした実験的なタイプレベルのRubyインタープリターです。
それは抽象解釈によって機能し、より良い洗練に向けた第一歩を踏み出しているので、制作目的で使用するときは注意してください。
インストールするには、プロジェクトにgemを追加するだけです:
gem install typeprof
2.7以降のRubyバージョンが必要であることに注意してください。
次のバージョンのAnimal
クラス:
class Animal
def initialize(weight)
@weight = weight
end
def die(age)
if age > 50
true
elsif age <= 50
false
elsif age < 0
nil
end
end
end
Animal.new(100).die(65)
メソッドage
内で何が起こっているかに基づいて さらに同じメソッドを呼び出すと、TypeProfはコードで操作されたタイプをスマートに推測できます。
typeprof animal.rb
を実行すると コマンド、それは出力であるはずです:
## Classes
class Animal
@weight: Integer
def initialize: (Integer weight) -> Integer
def die: (Integer age) -> bool?
end
これは、すでに多くのコードが実行されているプロジェクトに提供できる強力なツールです。
VSコード統合
現在、フォーマットや構造チェックなどのためにRBSを処理するために利用できるVSCodeプラグインは多くありません。特にそれがまだ比較的新しいためです。
ただし、ストアで「RBS」を検索すると、 ruby-signatureというプラグインが1つ見つかる場合があります。 これは、以下に示すように、構文の強調表示に役立ちます。
VSコードでのRBS構文の強調表示。
RBSは非常に新鮮で、より安全なRubyコードベースに向けた重要なステップをすでに表しています。
通常、Ruby onRailsアプリケーション用のRBSファイルを生成するためのRBSRailsなど、新しいツールやオープンソースプロジェクトがやがてそれをバックアップするようになります。
将来は、より安全でバグのないアプリケーションを備えたRubyコミュニティにとって素晴らしいものになるでしょう。それを見るのが待ちきれません!
-
Rubyでの挿入ソートを理解する
注:これは、Rubyを使用したさまざまなソートアルゴリズムの実装を検討するシリーズのパート4です。パート1ではバブルソート、パート2では選択ソート、パート3ではマージソートについて説明しました。 データを並べ替えるためのさまざまな方法を引き続き検討するため、挿入並べ替えに目を向けます。挿入ソートが好きな理由はたくさんあります!まず、挿入ソートは安定です。 、これは、等しいキーを持つ要素の相対的な順序を変更しないことを意味します。 インプレースアルゴリズムでもあります 、は、並べ替えられた要素を格納するための新しい配列を作成しないことを意味します。最後に、挿入ソートは、すぐにわかるように、実
-
Ruby2.6の9つの新機能
Rubyの新しいバージョンには、新しい機能とパフォーマンスの改善が含まれています。 変更についていきますか? 見てみましょう! 無限の範囲 Ruby 2.5以前のバージョンは、すでに1つの形式の無限範囲をサポートしています( Float ::INFINITY を使用) )、しかしRuby2.6はこれを次のレベルに引き上げます。 新しい無限の範囲 次のようになります: (1..) これは、(1..10)のような終了値がないため、通常の範囲とは異なります。 。 使用例 : [a, b, c].zip(1..) # [[a, 1], [b, 2], [c, 3]] [1,2,3,