0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Ruby】Steep に `::Numeric` does not have method `*` と言われた

0
Last updated at Posted at 2026-03-11

そろそろ RBS と Steep について勉強しておくか,と思ったら第一歩からつまづいた話。

試す

単純なスクリプトを用意

こういうスクリプトがあったとする:

lib/m.rb
module M
  def double(x)
    x * 2
  end
end

x は数値でも文字列でも配列でも動作するが,ひとまず Integer 前提で実装したとしよう。

RBS を書く

先ほどのスクリプトの RBS を書く。

よく知らないが,RBS ファイルは,パスの libsig に変え,拡張子の .rb.rbs に変えたところに書く慣習があるらしいので,それに倣ってみる。

入門記事などによると,以下のように書けばよいはずだ。

sig/m.rbs
module M
  def double: (::Integer) -> ::Integer
end

(現時点で Qiita は RBS のシンタックスハイライトに対応していないようだ,悲しい)

モジュール M を使うコードはまだ書いてないが,この時点でも Steep による検査は可能だ。

Steep で検査

Steep で検査してみる:

steep check

本来はまず steep init して Steepfile というファイルを生成し,それを適切に編集してから行う。
実際,Steepfile 無しでやったので

ERROR: [Steep 1.10.0] Cannot find a configuration at Steepfile: steep init to scaffold.

というエラーは出る。
しかし,それでも一応検査はできるようだ。

検査結果として

No type error detected.

が出た。

ここまでは順調であった。

型を少し変えてみる

double メソッドの仕様として,Integer だけでなく他の数値型も OK としたい。

モジュール M の定義はそのままでいい。

RBS を変更

RBS の IntegerNumeric に変えればよいはずだ。

sig/m.rbs
module M
  def double: (::Numeric) -> ::Numeric
end

再び Steep で検査

これで

steep check

してみる。

おやっ?

lib/m.rb:3:6: [error] Type `::Numeric` does not have method `*`
│ Diagnostic ID: Ruby::NoMethod
│
└     x * 2
        ~

Detected 1 problem from 1 file

ど,どういうことだ?

Numeric が * メソッドを持っていないだと?
んなワケあるかいっ!

Numeric に * は無いのか

いや,Numeric* が定義されていないのは事実だ。

Numeric は抽象クラスであって,ここに直接定義されていない数値演算メソッドはいくつもある1

* はサブクラスでそれぞれ定義されていて,Numeric には定義されていない。

だから,Steep が言っていることはいちおう筋が通っている。

しかし納得しがたいものはある。
これを Steep の限界と見るべきか Numeric の不備2と見るべきかはよく分からないが。

どう書けばよいのか

複数列挙

RBS では,型を | で繋いで併記することができる。
Integer でも Float でもよい,となると以下のように書く。

sig/m.rbs
module M
  def double: (::Integer | ::Float) -> ::Numeric
end

これならば検査にパスする。
しかし,数値クラスとしては Rational もあるし,場合によっては Complex にも対応させたいかもしれない。
BigDecimal はどうするんだ?

それら全ての可能性があるなら全部書くわけ?

Numeric の RBS を書く

他の方法を探る。

Numeric には * は定義されていないが,必ず(サブクラスが)* を持つ。
それならば Numeric の RBS を書いて * を定義してやればいいのでは?
以下のファイルを用意する:

sig/numeric.rbs
class Numeric
  def *: (::Numeric) -> ::Numeric
end

これで steep check すれば,モジュール M の型定義が

sig/m.rbs
module M
  def double: (::Numeric) -> ::Numeric
end

のままでも検査にパスするようになった。

めでたしめでたし。
……なのか?

::Numeric does not have method... と言われるたびにこういう型定義を記述しなければならないのか?

real を指定(追記 1)

(本節は投稿後に追記)

投稿後に real という型を知った。これは

と定義されており,要するに Integer か Float か Rational か,ということらしい。
言い換えれば,Numeric の組込みサブクラスで Complex を除いた全て,ということになる。

確かに「実数のすべての数値型」にしたいことは非常に多い。
今回のケースもそうだと仮定すると

sig/m.rbs
module M
  def double: (real) -> real
end

と書けばよいわけだ。

ただ,Complex や BigDecimal にも対応させたい場合は使えない。

型を定義(追記 2)

前節の結果を見て,「もしや型って自分で定義できるのでは?」と思った。

試しに

sig/m.rbs
module M
  type numeric = Integer | Float | Rational | Complex
  
  def double: (numeric) -> numeric
end

とやってみたら,これで検査にパスした。
こういうこともできるのかあ。ただ乱用はマズいかもね。

感想

型定義を整備していけば開発が捗るらしい,というのは何となく分かるのだが,型定義を書くのがめんどくさすぎると思った。

  1. Numeric に定義されたメソッドもある。例えば絶対値を返す abs で,これは負数かどうかの判定と符号反転を用いて実装されている。

  2. 素人考えだが,数値クラスは * を持つはずなので型検査だけのための仮の Numeric#* が定義されていればいいのではと思ったり。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?