Skip to content

Commit

Permalink
Prepare for 0.3.0 release
Browse files Browse the repository at this point in the history
  • Loading branch information
mwpastore committed Sep 19, 2016
1 parent b647500 commit 3b1516b
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 36 deletions.
72 changes: 46 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
```
Expand Down Expand Up @@ -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'
Expand All @@ -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(
Expand All @@ -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:
Expand All @@ -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:

Expand All @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion ioughta.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
spec.authors = ['Mike Pastore']
spec.email = ['[email protected]']

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'

Expand Down
14 changes: 6 additions & 8 deletions lib/ioughta.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ 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

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
Expand All @@ -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

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/ioughta/version.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# frozen_string_literal: false
module Ioughta
VERSION = '0.2.2'.freeze
VERSION = '0.3.0'.freeze
end

0 comments on commit 3b1516b

Please sign in to comment.