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