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