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

RBS:動作中の新しいRuby3タイピング言語

待望のRubyバージョン3.0.0がついにリリースされました。以前のバージョンと比較して3倍高速なパフォーマンスの向上、同時実行並列の実験機能など、多くの大幅な改善に加えて、RubyチームはRubyでの動的型付けのための新しい構文言語RBSも導入しました。

これは、Sorbetなどの静的型チェック用のコミュニティ開発ツールの成功に基づいて、チームが何年にもわたって話し合っていたものでした。

Sorbetは、Stripeに裏打ちされた強力なタイプチェッカーです。 RBIファイルに注釈を付けたり定義したりしてコードをチェックします。次に、RBIファイルは、静的コンポーネントと動的コンポーネントの間のインターフェイスとして機能し、それらの「説明」(定数、祖先、メタプログラミングコードなど)を提供します。

それで、Sorbetが主に静的チェックを扱い、RBSが動的型付けに対処するように作られた場合、それらの違いは何ですか?それらはどのように共存しますか?どちらを使用する必要がありますか?

これらは、RBSの主要な役割に関するかなり一般的な質問です。そのため、この作品を書くことにしました。実際に、それが何ができるかに基づいてそれを採用することを検討すべき理由を明確にするために。さっそく飛び込みましょう!

基本から始める

静的型付けとの違いを明確に理解することから始めましょう。 動的型付け_。基本ですが、RBSの役割を理解するために把握することが重要な概念です。

静的に型付けされた言語のコードスニペットを参照として取り上げましょう:

➜
String str = "";
str = 2.4;

そのような言語がそのオブジェクトと変数のタイプを気にするのはニュースではありません。そうは言っても、上記のようなコードはエラーをスローします。

Rubyは、JavaScript、Python、Objective-Cなどの他の多くの言語と同様に、オブジェクトのターゲットとするタイプにそれほど注意を払っていません。

以下に示すように、Rubyの同じコードが正常に実行されます。

➜  irb
str = ""
str = 2.4
puts str # prints 2.4

これが可能なのは、Rubyのインタプリタが動的にする方法を知っているからです。 あるタイプから別のタイプに切り替えます。

ただし、通訳者が許可するものには制限があります。たとえば、次のコード変更を行います。

➜  irb
val = "6.0"
result = val + 2.0
puts result

これにより、次のエラーが発生します。

エラー:FloatからStringへの暗黙的な変換はありません

たとえば、JavaScriptで同じコードを実行すると、問題なく実行されます。

話の教訓:Rubyは実際に型を動的に推測します。ただし、他の主要な動的言語とは異なり、すべてを受け入れるわけではありません。気をつけて!

そこで、タイプチェッカー(静的または動的)が役立ちます。

RBSとシャーベット

そうです、動的なものと静的なものについてあなたの意見があります。しかし、シャーベットはどうですか?非推奨になりますか?

全くない。 RBSとSorbetの主な(そしておそらく最も重要な)違いは、前者は単なる言語であり、後者は完全な型チェッカーそのものであるということです。

Rubyチームは、構造を説明するというRBSの主な目標を主張しています。 あなたのコードの。型チェックは実行されませんが、型チェッカー(Sorbetなど)が型チェックに使用できる構造を定義します。コード構造は、新しいファイル拡張子( .rbs )内に保存されます。 。

確認するために、次のRubyクラスを例として取り上げましょう。

class Super
    def initialize(val)
      @val = val
    end
 
 
    def val?
      @val
    end
end
 
class Test < Super
  def initialize(val, flag)
    super(val)
    @flag = flag
  end
 
  def flag?
    @flag
  end
end

これは、Rubyの単純な継承を表しています。ここで注目すべき興味深い点は、flagを除いて、クラスで使用されている各属性のタイプを推測できないことです。 。

flag以降 デフォルト値で初期化され、開発者とタイプチェッカーの両方がタイプを推測して、さらなる誤用を防ぐことができます。

以下は、RBS形式での上記のクラスの適切な表現です。

class Super
  attr_reader val : untyped
 
  def initialize : (val: untyped) -> void
end
 
class Test < Super
  attr_reader flag : bool
 
  def initialize : (val: untyped, ?flag: bool) -> void
  def flag? : () -> bool
end

これを消化するために少し時間がかかります。これは宣言言語であるため、RBSファイルに表示されるのは署名のみです。簡単ですね。

CLIツール(後で詳しく説明します)によって自動生成された場合でも、ユーザーによって自動生成された場合でも、型にuntypedとして注釈を付ける方が安全です。 推測できないとき。

valのタイプについて確信がある場合 たとえば、RBSマッピングを次のように切り替えることができます。

class Super
  attr_reader val : Integer
 
  def initialize : (val: Integer) -> void
end

また、RubyチームとSorbetチームの両方が、RBSの作成と改善に向けて取り組んでいた(そして現在も取り組んでいる)ことに注意することも重要です。 Rubyチームがこのプロジェクトで多くのものを微調整するのに役立ったのは、何年にもわたってタイプチェックに関するSorbetチームの経験でした。

RBSファイルとRBIファイル間の相互運用性はまだ開発中です。目標は、Sorbetおよびその他のチェッカーツールが公式かつ一元化された基盤を持つことです。

RBSCLIツール

RBSを開発するときにRubyチームが持っていた重要な考慮事項の1つは、開発者がそれを試して使用方法を学ぶのに役立つCLIツールを出荷することでした。 rbsと呼ばれます デフォルトではRuby3に付属しています。Rubyバージョンをまだアップグレードしていない場合は、そのgemをプロジェクトに直接追加することもできます。

➜  gem install rbs

コマンドrbs help コマンドの使用法と使用可能なコマンドが表示されます。

使用可能なコマンドのリスト

これらのコマンドのほとんどは、Rubyコード構造の解析と分析に重点を置いています。たとえば、コマンドancestors 特定のクラスの階層構造をスイープして、その祖先をチェックします。

➜  rbs ancestors ::String
::String
::Comparable
::Object
::Kernel
::BasicObject

コマンドmethods 特定のクラスのすべてのメソッド構造を表示します:

➜  rbs methods ::String
! (public)
!= (public)
!~ (public)
...
Array (private)
Complex (private)
Float (private)
...
autoload? (private)
b (public)
between? (public)
...

特定のメソッド構造を見たいですか? methodに移動します :

➜  rbs method ::String split
::String#split
  defined_in: ::String
  implementation: ::String
  accessibility: public
  types:
      (?::Regexp | ::string pattern, ?::int limit) -> ::Array[::String]
    | (?::Regexp | ::string pattern, ?::int limit) { (::String) -> void } -> self

今日RBSを開始する場合は、コマンドprototype すでに存在するクラスのスキャフォールディングタイプで大いに役立ちます。このコマンドは、RBSファイルのプロトタイプを生成します。

前のTest < Superを見てみましょう 継承の例とコードをappsignal.rbというファイルに保存します 。次に、次のコマンドを実行します。

➜  rbs prototype rb appsignal.rb

コマンドはrbを許可するので 、 rbi 、およびランタイム ジェネレーターの場合、prototypeの直後にスキャフォールディングしている特定のタイプのファイルを提供する必要があります コマンドの後にファイルパス名が続きます。

実行の結果は次のとおりです。

class Super
  def initialize: (untyped val) -> untyped
 
  def val?: () -> untyped
end
 
class Test < Super
  def initialize: (untyped val, ?flag: bool flag) -> untyped
 
  def flag?: () -> untyped
end

最初のRBSバージョンと非常によく似ています。前述のように、ツールはuntypedとしてマークされます 推測できなかったタイプ。

また、メソッドの戻りもカウントされます。 flagの戻りタイプに注意してください 意味。開発者は、メソッドが常にブール値を返すことを確信していると思いますが、Rubyの動的な性質により、ツールは100%ブール値であるとは言えません。

そして、それは別のRuby3の子供が救助に来るときです:TypeProf。

TypeProfツール

TypeProfは、構文ツリーの解釈に基づいて作成されたRubyの型分析ツールです。

まだ実験的ですが、コードが何をしようとしているのかを理解することになると、非常に強力であることが証明されています。

Ruby 3をまだお持ちでない場合は、プロジェクトにgemを追加するだけです。

➜  gem install typeprof

それでは、同じ appsignal.rbを実行してみましょう。 それに対してファイルする:

➜  typeprof appsignal.rb

これが出力です:

# Classes
class Super
  @val: untyped
  def initialize: (untyped) -> untyped
  def val?: -> untyped
end
 
class Test < Super
  @val: untyped
  @flag: true
 
  def initialize: (untyped, ?flag: true) -> true
  def flag?: -> true
end

flagに注意してください 現在マッピングされています。これが可能なのは、RBSプロトタイプが行うこととは異なり、TypeProfがメソッドの本体をスキャンして、その特定の変数に対して実行されているアクションを理解しようとするためです。この変数への直接の変更を識別できなかったため、TypeProfはメソッドreturnをブール値として安全にマッピングしました。

たとえば、TypeProfは、Testをインスタンス化して使用する他のクラスにアクセスできると考えてください。 クラス。これがあれば、コードをさらに深く掘り下げて、予測を微調整することができます。次のコードスニペットがappsignal.rbの最後に追加されているとします。 ファイル:

testSub = Test.new("My value", "My value" == "")
testSup = Super.new("My value")

そして、initializeを変更したこと 次のメソッドシグネチャ:

def initialize(val, flag)

コマンドを再実行すると、次のように出力されます。

# Classes
class Super
  @val: String
 
  def initialize: (String) -> String
  def val?: -> String
end
 
class Test < Super
  @val: String
  @flag: bool
 
  def initialize: (String val, bool flag) -> bool
  def flag?: -> bool
end

超かっこいい!

TypeProfは、継承された属性をうまく処理できません。そのため、新しいSuperをインスタンス化しています。 物体。そうしないと、そのvalを取得できません。 Stringです 。

TypeProfの主な利点はその安全性です。何かを確実に理解できないときはいつでも、untyped 返送されます。

部分RBS仕様

公式ドキュメントからの重要な警告の1つは、TypeProfは非常に強力ですが、RBSコードに関して生成できるものとできないものに関する制限に注意する必要があると述べています。

たとえば、Ruby開発者の間で一般的に行われているのは、引数に応じてメソッドのさまざまな動作を呼び出すメソッドのオーバーロードです。

新しいメソッドspellを考えてみましょう Superに追加されます Integerを返すクラス。 またはString パラメータタイプに基づく:

def spell(val)
  if val.is_a?(String)
    ""
  else
    0
  end
end

RBSは、共用体型(複数の可能な型を表す値)構文を介して過負荷に対処できるようにすることで、この手法を採用しています。

def spell: (String) -> String | (Integer) -> Integer

TypeProfは、メソッドの本体を分析するだけではこれを推測できません。それを助けるために、そのような定義を手動でRBSファイルに追加することができ、TypeProfは常に最初にそこで指示をチェックします。

このためには、コマンドの最後にRBSファイルパスを追加する必要があります:

typeprof appsignal.rb appsignal.rbs

以下の新しい出力を参照してください:

class Super
  ...
  def spell: (untyped val) -> (Integer | String)
end

さらに、Kernel#pを使用して、実行時に実際の型を確認することもできます。 appsignal.rb の最後に次の2行を追加して、オーバーロードが機能しているかどうかをテストします。 ファイル:

p testSup.spell(42)
p testSup.spell("str")

これが出力になります:

# Revealed types
#  appsignal.rb:11 #=> Integer
#  appsignal.rb:12 #=> String
 
...

詳細、特にTypeProfの制限に関するセクションについては、公式ドキュメントを参照してください。

ダックタイピング

あなたは前にそれについて聞いたことがあります。 Rubyオブジェクトがアヒルが行うすべてのことを行う場合、それはアヒルです。

これまで見てきたように、Rubyはオブジェクトが何であるかを気にしません。タイプはオブジェクト参照と同様に動的に変更できます。

ダックタイピングは役に立ちますが、注意が必要な場合があります。例を見てみましょう。

これから、val Superに対して宣言した属性 Stringであるクラス 、常に整数に変換可能である必要があります。

開発者が常に変換を保証することを信頼するのではなく(おそらく、そうでなければエラーをスローする)、インターフェイスを作成できます。 それを述べる:

interface _IntegerConvertible
   def to_int: () -> Integer
end

インターフェイスタイプは、具象クラスおよびモジュールから切り離された1つ以上のメソッドを提供します。このように、特定のタイプをスーパーインスタンス化に渡す場合は、次のようにするだけです。

class Super
  attr_reader val : _IntegerConvertible
 
  def initialize : (val: _IntegerConvertible) -> void
end

このインターフェースを実装する具体的なクラスまたはモジュールは、適切な検証が行われていることを確認する必要があります。

メタプログラミング

おそらく、Rubyの最も動的な機能の1つは、実行時にそれ自体でコードを作成するコードを作成する機能です。それがメタプログラミングです。

物事の性質が不確実であるため、RBSCLIツールはメタプログラミングコードからRBSを生成できません。

例として次のスニペットを取り上げましょう:

class Test
    define_method :multiply do |*args|
        args.inject(1, :*)
    end
end
 
p Test.new.multiply(2, 3, 5)

このクラスは、multiplyと呼ばれるメソッドを定義します 実行時に引数を挿入し、それぞれに前の結果を乗算するように指示します。

RBSprototypeを実行したら コマンドの場合、これは出力になります:

class Test
end

メタプログラミングコードの複雑さに応じて、TypeProfはそれから何かを抽出するために最善を尽くします。ただし、常に保証されるわけではありません。

いつでも独自のタイプマッピングをRBSファイルに追加でき、TypeProfは事前にそれらに従うことを忘れないでください。これはメタプログラミングにも有効です。

チームはメタプログラミングの更新を含む可能性のある新機能を絶えずリリースしているため、最新のリポジトリの変更を常に更新しておくことも重要です。

そうは言っても、コードベースに何らかのメタプログラミングが含まれている場合は、これらのツールに注意してください。盲目的に使用しないでください!

まとめ

これまでに説明した内容や、RBSとTypeProfの両方のエッジのユースケースについて知っておくべき詳細がたくさんあります。

そのため、詳細については公式ドキュメントを参照してください。

RBSはまだ非常に新鮮ですが、他のツールでコードベースをタイプチェックすることに慣れているRubyistにすでに大きな影響を与えています。

あなたはどうですか?試してみましたか? RBSについてどう思いますか?

P.S。 Ruby Magicの投稿をマスコミから離れたらすぐに読みたい場合は、Ruby Magicニュースレターを購読して、投稿を1つも見逃さないでください。


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

  2. 間違った文字を入力するキーボード キーを修正する方法

    キーボードで「p」を押すと、画面に別のアルファベットや記号が出力されるということはありませんか?奇妙に思えるかもしれませんが、キーボードは何年にもわたって非常に信頼性が高いにもかかわらず、しばらくするとハードウェアの不具合が発生する可能性があることを理解してください.キーによっては不快な音がしたり、反応しなくなったりするものもあります。ただし、このブログでは、間違った文字を入力するキーボード キーを修正する方法を説明します。 言語設定、アクティブな NumLock、マルウェアの侵入、古いキーボード ドライバー、または単にキーボードを完全に交換する必要があるなどの理由が考えられます。心配しない