Today I realised that Ruby's
>=
is more useful than~>
.
Rubygems’s pessimistic operator ~>
, also known as “twiddle-wakka”, allows developers to specify flexible gem version ranges.
This... | Is equivalent to... |
---|---|
gem 'i18n', '~> 2.3.5' |
gem 'i18n', ‘>= 2.3.5’, '< 2.4.0' |
gem 'i18n', '~> 2.3' |
gem 'i18n', ‘>= 2.3.0’, ‘< 3’ |
gem 'i18n', '~> 2' |
gem 'i18n', '>= 2.0.0' |
While many gems prescribe the use of ~>
, it may be a good choice to opt for >=
instead to avoid some of the pitfalls with Ruby’s pessimistic operator.
When matching full versions (eg, ~> 4.7.1
), it may be restricting updates to future versions unintentionally.
gem 'pry', '~> 4.7.1' # same as '>= 4.7.1', '< 4.8'
When matching full versions (eg ~> 4.7.1
), it will restrict matching to the 4.7 series and avoid upgrading to 4.8. This makes little sense in Semver: newer major releases (eg, 4.7, 4.8, 4.9…) should be considered backward-compatible with older releases in the same version (4.1).
When trying to update patch versions (eg, 5.2.0 to 5.2.3), using the pessimistic operator can lead to some confusion.
gem 'sprockets', '~> 5.2' # same as '>= 5.2.0', '< 6'
When a gem is matched pessimistically to a minor version (eg ~> 5.2), and a new update is available (eg, 5.2.3), it may be tempting to bump the version.
- gem 'sprockets', '~> 5.2'
+ gem 'sprockets', '~> 5.2.3' # (!) same as '>= 5.2.3', '< 5.3'
Doing this would have an unintendend side effect: it will restrict upgrading to version 5.3, which wasn’t intended in the previous revision. The correct fix is to change it to use the >=
operator:
- gem 'sprockets', '~> 5.2'
+ gem 'sprockets', '>= 5.2.2', '< 6.0'
When matching versions before 1.0, it may eagerly match incompatible versions unintentionally.
gem 'geocoder', '~> 0.7' # (!) same as '>= 0.7.0', '< 1'
Specifying pre-1.0 versions as ~> 0.7
is common, but semver discourages this. Every minor version before 1.0 is considered to be a breaking version. That is, upgrading from 0.7 to 0.8 should be considered in the same regard as 1.0 to 2.0. Semver spec has this:
“Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable.”
A more reasonable approach might be to specify an upper bound:
gem 'geocoder', '>= 0.7.0', '< 0.8'
# or
gem 'geocoder', '~> 0.7.0'
Node.js also supports the ~
operator for semver ranges. However, it’s use is generally discouraged, as a newer ^
(caret) operator was introduced to fix the faults of the ~ operator. Unlike the Ruby pessismistic operator, the caret will always pick compatible versions based on Semver’s specification.
^1.2.3 := >=1.2.3, <2.0.0
^1.2 := >=1.2.0, <2.0.0
^1 := >=1.0.0, <2.0.0
^0.2.3 := >=0.2.3, <0.3.0
^0.2 := >=0.2.0, <0.3.0
^0.0.3 := >=0.0.3, <0.0.4
^0.0 := >=0.0.0, <0.1.0
^0 := >=0.0.0, <1.0.0
There were a few discussions in the Node.js community that might be worth pursuing:
Rust’s Cargo also adopts and encourages the use of the ^
specifier.
The caret operator exists in other languages and package managers:
Ruby doesn’t have a ^ operator, but it can be emulated using a combination of >=and < specifiers.
Instead of… | Consider instead... |
---|---|
gem 'xxx', '~> 2.4' gem 'xxx', '~> 2.4.5' |
gem 'xxx', '>= 2.4.0' gem 'xxx', '>= 2.4.0', '< 3' |
- The
~>
was also called “spermy operator” in 2011. - what-do-you-call-this-in-ruby claims it was also called “tadpole” once.
@rstacruz Elixir's mix does not support caret operator. So I just opened a PR for this: elixir-lang/elixir#11448