From 3b1516b7e915f66bb31a60dbd63fa534280c9cfb Mon Sep 17 00:00:00 2001 From: Mike Pastore Date: Mon, 19 Sep 2016 16:57:06 -0500 Subject: [PATCH] Prepare for 0.3.0 release --- README.md | 72 +++++++++++++++++++++++++++--------------- ioughta.gemspec | 2 +- lib/ioughta.rb | 14 ++++---- lib/ioughta/version.rb | 2 +- 4 files changed, 54 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 30d03fe..731f1ce 100644 --- a/README.md +++ b/README.md @@ -3,28 +3,36 @@ [![Build Status](https://travis-ci.org/mwpastore/ioughta.svg?branch=master)](https://travis-ci.org/mwpastore/ioughta) [![Gem Version](https://badge.fury.io/rb/ioughta.svg)](https://badge.fury.io/rb/ioughta) -Helpers for defining Go-like constants and hashes in Ruby using iota. +Helpers for defining sequences of constants in Ruby using a Go-like syntax. Go has quite a nice facility for defining constants derived from a sequential value using a [simple and elegant syntax][1], so I thought I'd steal it for Ruby. Rubyists tend to group constants together in hashes rather than littering their programs with countless constants, so there's a mechanism for that, too. -Here's an example, written in Go: +Although there isn't as strong of a need for sequences of constants in Ruby as +there is in other languages such as Go, they are still sometimes required when +working with external systems such as databases and web APIs for which Ruby +symbols don't map cleanly. For example, a database column might store users' +privilege levels as 0, 1, or 2, and it would be useful to define constants that +map to those values. Ruby doesn't have a native expression for this construct +(other than simply defining them one at a time). + +Here's a simple example, written in Go: ```go type Allergen int const ( - IgEggs Allergen = 1 << iota // 1 << 0 which is 00000001 - IgChocolate // 1 << 1 which is 00000010 - IgNuts // 1 << 2 which is 00000100 - IgStrawberries // 1 << 3 which is 00001000 - IgShellfish // 1 << 4 which is 00010000 + IgEggs Allergen = 1 << iota // 1 << 0 which is 00000001 + IgChocolate // 1 << 1 which is 00000010 + IgNuts // 1 << 2 which is 00000100 + IgStrawberries // 1 << 3 which is 00001000 + IgShellfish // 1 << 4 which is 00010000 ) ``` -Here it is in Ruby, using ioughta: +And here it is in Ruby, using ioughta: ```ruby Object.ioughta_const( @@ -41,13 +49,13 @@ IG_STRAWBERRIES # => 8 Or, perhaps a bit more Rubyishly: ```ruby -IG = Object.ioughta_hash(->(i) { 1 << i }, %i[ +IG = Object.iota_hash(%i[ eggs chocolate nuts strawberries shellfish -]).freeze +]) { |i| 1 << i }.freeze IG[:shellfish] # => 16 ``` @@ -76,9 +84,11 @@ $ gem install ioughta Ioughta works just like `const` and `iota` do in Go, with only a few minor differences. You must `include` the module in your program, class, or module in -order to start using it. The iterator starts at zero (`0`) and increments for -each constant. The default lambda is simply `:itself`, so you can very easily -create a sequence of constants with consecutive integer values: +order to start using it. The iterator starts at zero (0) and increments for +each constant (or hash key) being defined. A function (any Ruby callable) takes +the current iteration as input and returns the value to be assigned. The +default function simply returns the iterator, so you can easily create +sequences of constants with consecutive integer values: ```ruby require 'ioughta' @@ -101,10 +111,9 @@ BAR # => 2 QUX # => 4 ``` -As soon as Ioughta sees a lambda, it will start using it to generate future -values from the iterator. In Go parlance, this is (apparently) known as -*implicit repetition of the last non-empty expression list*. You can redefine -the lambda as many times as you like: +As soon as Ioughta sees a lambda (or any Ruby callable), it will start using it +to generate future values from the iterator. You can redefine the lambda as +many times as you like: ```ruby Object.ioughta_const( @@ -114,14 +123,15 @@ Object.ioughta_const( :D, ->(j) { j ** 3 }, # will cube (3 => 27) :E, # will also cube (4 => 64) :F, # cube all the things (5 => 125) - :G, proc(&:itself) # restore the default behavior (6 => 6) + :G, ->{ 0.5 } # will use a simple value (6 => 0.5) + :H, proc(&:itself) # restore the default behavior (7 => 7) ) ``` You can also pass the lambda as the first argument: ```ruby -Object.ioughta_const ->(i) { 1 << (10 * i) }, %i[_ KB MB GB TB PB EB ZB YB] +Object.Ioughta_const ->(I) { 1 << (10 * I) }, %I[_ KIB MIB GIB TIB PIB EIB ZIB YIB] ``` Or even pass a block, instead of a lambda: @@ -130,19 +140,25 @@ Or even pass a block, instead of a lambda: BYTES = Object.ioughta_hash(%i[_ KB MB GB TB PB EB ZB YB]) { |i| 10 ** (i * 3) }.freeze ``` +If the first argument is a lambda *and* a block is given, the block will be +silently ignored. + +## Notes + The only major feature missing from the Go implementation is the ability to perform parallel assignment in the constant list. We're defining a list of terms, not a list of expressions, so it's not possible to do in Ruby without -resourcing to nasty `eval` tricks. Don't forget to separate your terms with -commas! +resourcing to nasty `eval` tricks. **Don't forget to separate your terms with +commas and freeze your hash constants!** You've probably noticed that in order to use Ioughta in the top-level namespace, we need to explicitly specify the `Object` receiver (just like we need to do for `#const_set`). I didn't want to get too crazy with the -monkeypatching and/or delegation. No such limitation exists when including -Ioughta in a module or class, thanks to the available context. Also, if the -`ioughta_const` and `ioughta_hash` methods are too ugly for you (I don't blame -you), they're aliased as `iota_const` and `iota_hash`, respectively. +monkey-patching and/or method delegation. No such limitation exists when +including Ioughta in a module or class, thanks to the available context. Also, +if the `ioughta_const` and `ioughta_hash` method names are too ugly for you (I +don't blame you), they're aliased as `iota_const` and `iota_hash`, +respectively. Here is a very contrived and arbitrary example: @@ -167,7 +183,7 @@ MyFileUtils.mask_and_shift(0644, :user) & MyFileUtils::EXECUTE # => 0 MyFileUtils.mask_and_shift(01777, :special) & MyFileUtils::TACKY # => 1 ``` -One note on the above: the lambda (or block) can take the key at the current +One note on the above: the lambda (or block) can take the "key" at the current iteration as an optional second argument. ## Development @@ -181,6 +197,10 @@ release a new version, update the version number in `version.rb`, and then run git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). +## Trivium + +Pronounced /aɪ ˈɔtə/, as in the English phrase "Why, I oughta...!" + ## Contributing Bug reports and pull requests are welcome on GitHub at diff --git a/ioughta.gemspec b/ioughta.gemspec index 00b3e52..584e288 100644 --- a/ioughta.gemspec +++ b/ioughta.gemspec @@ -10,7 +10,7 @@ Gem::Specification.new do |spec| spec.authors = ['Mike Pastore'] spec.email = ['mike@oobak.org'] - spec.summary = 'Helpers for defining Go-like constants and hashes using iota' + spec.summary = 'Helpers for defining sequences of constants in Ruby using a Go-like syntax' spec.homepage = 'http://github.com/mwpastore/ioughta' spec.license = 'MIT' diff --git a/lib/ioughta.rb b/lib/ioughta.rb index 48f304a..9d8b572 100644 --- a/lib/ioughta.rb +++ b/lib/ioughta.rb @@ -5,7 +5,7 @@ module Ioughta def self.included(base) class << base def ioughta_const(*data, &block) - each_resolved_pair(data, block) do |nom, val| + each_resolved_ipair(data, block) do |nom, val| const_set(nom, val) end end @@ -13,7 +13,7 @@ def ioughta_const(*data, &block) alias_method :iota_const, :ioughta_const def ioughta_hash(*data, &block) - each_resolved_pair(data, block).to_h + each_resolved_ipair(data, block).to_h end alias_method :iota_hash, :ioughta_hash @@ -23,9 +23,7 @@ def ioughta_hash(*data, &block) DEFAULT_LAMBDA = proc(&:itself) SKIP_SYMBOL = :_ - def each_pair_with_index(data, block = nil) - return enum_for(__method__, data, block) unless block_given? - + def each_ipair_with_index(data, block = nil) data = data.to_a.flatten(1) lam = (data.shift if data[0].respond_to?(:call)) || block || DEFAULT_LAMBDA @@ -34,11 +32,11 @@ def each_pair_with_index(data, block = nil) end end - def each_resolved_pair(*args) + def each_resolved_ipair(*args) return enum_for(__method__, *args) unless block_given? - each_pair_with_index(*args) do |nom, lam, iota| - yield nom, lam[*[iota, nom].take(lam.arity.abs)] unless nom == SKIP_SYMBOL + each_ipair_with_index(*args) do |nom, lam, iota| + yield nom, lam.call(*[iota, nom].take(lam.arity.abs)) unless nom == SKIP_SYMBOL end end end diff --git a/lib/ioughta/version.rb b/lib/ioughta/version.rb index 6e532ce..76a0ab9 100644 --- a/lib/ioughta/version.rb +++ b/lib/ioughta/version.rb @@ -1,4 +1,4 @@ # frozen_string_literal: false module Ioughta - VERSION = '0.2.2'.freeze + VERSION = '0.3.0'.freeze end