Open14

RBS使いながらのメモ

北市真北市真
row[1..].each_with_index do |item, index|
  # ...
end

と書くと、row[1..]nilかも知れないのでエラーが出る。
row[1..]nilにならないと確信している時、どう書いたらいいのだろう。

Steep向けに

items = row[1..] #: Array[String]
items.each_with_index do |item, index|
  # ...
end

みたいに、一回変数に代入したら型を指定できるのだけど、シグニチャーチェックのためにわざわざしたくない。

北市真北市真

これも同様:

    @base_uri = URI("https://example.com/")
    Net::HTTP.start(@base_uri.host, @base_uri.port, use_ssl: @base_uri.scheme == "https") do |http|
      # ...
    end

@base_uri.hostはシグニチャーとしては(::String | nil)になる。Net::HTTP.startの第一引数にnilを渡せないので、これはエラーになる。しかしこの場合は@base_uriは必ず::StringになるのでそのことをSteepに伝えたい。変数に割り当てたらいけるのは分かるけど・・・

北市真北市真

Arrow::Table.load時にカラムの型を指定できる?

北市真北市真

ファイルがCSVの場合はCSV.newへのオプションと同じ物を渡せる。ので:convertersを使っての変換はできる:

Arrow::Table.load("path/to/data.csv", converters: [:date])
Arrow::Table.load("path/to/data.csv", converters: [->(field) {field.to_s}])

カラムを指定して、「このカラムだけ」みたいなのはできなさそう・・・

北市真北市真

メソッドリファインメント(refineusing)ってどうやって書いたらいいの?

北市真北市真

Kernelじゃなくてmainのシグニチャーって提供されていない?
トップレベルでusingを使うとエラーになる。
(クラス定義内でusingを使う分にはエラーにならない。)

refineは問題無い。

北市真北市真

File.writePathnameを受け付けないことになっている。

シグニチャー(RBS v3.9.1):

io.rbs
def self.write: (String path, _ToS data, ?Integer offset, ?external_encoding: String | Encoding | nil, ?internal_encoding: String | Encoding | nil, ?encoding: String | Encoding | nil, ?textmode: boolish, ?binmode: boolish, ?autoclose: boolish, ?mode: String) -> Integer

単純にString | Pathnameにして大丈夫かな?

北市真北市真

タプルは扱えることになっている(Tuple type):

record = ["me", 12] #: [String, Integer]

けど、タプルをスプラットする時にSteepがシグニチャーを理解してくれないからエラーになる。
何言っているか分からないと思うけどこういうこと:

tuple.rb
def do_something(name, count)
  p [name, count]
end

record = ["me", 12] #: [String, Integer]
do_something(*record)

tuple.rbs
class Object
  def do_something: (String name, Integer count) -> void
end

% steep check tuple.rb
# Type checking files:

F

tuple.rb:6:13: [error] Cannot pass a value of type `[::String, ::Integer]` as an argument of type `::Array[[::String, ::Integer]]`
│   [::String, ::Integer] <: ::Array[[::String, ::Integer]]
│     (::String | ::Integer) <: [::String, ::Integer]
│       ::String <: [::String, ::Integer]
│
│ Diagnostic ID: Ruby::ArgumentTypeMismatch
│
└ do_something(*record)
               ~~~~~~~

Detected 1 problem from 1 file

CSVを扱う時によく使うパターンなので対応できると嬉しいが・・・。

北市真北市真

インスタンス変数のアノテーションってできない?

class App
  def run
    init
    files = @conn.mlsd("path/to/dir")
  ensure
    if @conn && !@conn.closed?
      @conn.close
    end
  end

  private

  def init
    @conn = Net::FTP.new("ho.st", "user", "password")
  end
end

class App
  @conn: Net::FTP | nil
end

とすると(初期化時は@connnilなので)、@conn.mlsdがエラーになってしまう、@connnilの時にmlsdメソッドが無いから。
これがローカル変数なら(Steepを使っている場合)

  # @type var conn: Net::FTP
  files = conn.mlsd("path/to/dir")

ってアノテーションできるんだけど、インスタンス変数はできない?
Steepのマニュアルではできることになっているんだけど、手許ではエラーになってしまう・・・。

北市真北市真
files = @conn.mlsd("path/to/dir")

の直前にアノテーションしてもだめだけど、メソッドの一行目に書けば大丈夫だった。

  def run
    # @type var conn: Net::FTP
    init
    files = @conn.mlsd("path/to/dir")