Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

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と入力するだけです。 コンソールで結果を確認します:

RBS、Rubysの新しいタイプ注釈システムを理解する 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は、パイプを介して結合するだけで、共用体タイプに対応します。 オペレーター。

メソッドのオーバーロード

(実際には多くのプログラミング言語の中で)別の一般的な方法は、メソッドのオーバーロードを許可することです。この場合、クラスは同じ名前の複数のメソッドを持つことができますが、シグネチャは異なります(パラメータのタイプや数、順序など)。 。

動物が最も近い進化のいとこを返すことができる例を見てみましょう:

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つ見つかる場合があります。 これは、以下に示すように、構文の強調表示に役立ちます。

RBS、Rubysの新しいタイプ注釈システムを理解する VSコードでのRBS構文の強調表示。

結論

RBSは非常に新鮮で、より安全なRubyコードベースに向けた重要なステップをすでに表しています。

通常、Ruby onRailsアプリケーション用のRBSファイルを生成するためのRBSRailsなど、新しいツールやオープンソースプロジェクトがやがてそれをバックアップするようになります。

将来は、より安全でバグのないアプリケーションを備えたRubyコミュニティにとって素晴らしいものになるでしょう。それを見るのが待ちきれません!


  1. Rubyでの挿入ソートを理解する

    注:これは、Rubyを使用したさまざまなソートアルゴリズムの実装を検討するシリーズのパート4です。パート1ではバブルソート、パート2では選択ソート、パート3ではマージソートについて説明しました。 データを並べ替えるためのさまざまな方法を引き続き検討するため、挿入並べ替えに目を向けます。挿入ソートが好きな理由はたくさんあります!まず、挿入ソートは安定です。 、これは、等しいキーを持つ要素の相対的な順序を変更しないことを意味します。 インプレースアルゴリズムでもあります 、は、並べ替えられた要素を格納するための新しい配列を作成しないことを意味します。最後に、挿入ソートは、すぐにわかるように、実

  2. 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,