Domain - Build data-types the easy way

This gem provides modules (module factories in fact) for creating domains, aka data-types, in a various common ways.

https://github.com/blambeau/domain

Scalar domains

A scalar domain has visible components that define the scalar structure of its values. For example a Point domain might have x and y components.

# Immediately create a Point class, that is, an implementation of the Point domain
# This is similar to `Struct.new(:x, :y)`
Point = Domain.scalar(:x, :y)

# Another way, if you want to define operators for the Point domain
class Point
  extend Domain::Scalar.new(:x, :y)

  def distance
    Math.sqrt(x*x + y*y)
  end
end

# in either case, `==`, `eql?` and `hash` are implemented in a consistent way
Point.new(1, 2) == Point.new(1, 2)

Reuse domains

A domain can also be built by reuse (the current implementation only support reusing a single value) For instance, a List domain could simply be implemented by reusing an Array.

class List
  extend Domain::Reuse.new(Array)

  def head
    # the reused instance is accessible under `reused_instance` (protected)
    reused_instance.first
  end

  # reuse through delegation to `reused_instance`
  delegate :each, :size, :empty?

  # similar to `delegate` but redecorate the resulting value (will yield Lists)
  reuse :map, :reject, :select
end

# The reused instance is expected to be passed at construction time
List.new [1, 2, 3]

# `==`, `eql?` and `hash` are already implemented in a consistent way
List.new([1, 2]) == List.new([1, 2])

Union domains

A union domain is simply a domain whose values are the union of other domains. The missing Boolean domain can simply be defined as follows:

# Factors a Boolean domain immediately
Boolean = Domain.union(TrueClass, FalseClass)

# or use the module factory in your own class
class Boolean
  extend Domain::Union.new(TrueClass, FalseClass)
end

# in either case, `===` is implemented in a consistent way
Boolean === true
Boolean === false

Specialization by constraints

A domain can also be built through specialization by constraint, that is specifying a predicate that will filter the values of a super domain. For instance:

# Factors the set of positive integers
PosInt = Domain.sbyc(Integer){|i| i > 0}

# or use the module factory (you'll have to extend Integer yourself for consistency)
class PosInt < Integer
  extend Domain::SByC.new(Integer){|i| i > 0}
end