domain 0.0.1 → 1.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +75 -2
- data/domain.noespec +1 -1
- data/lib/domain.rb +7 -2
- data/lib/domain/api.rb +48 -0
- data/lib/domain/factory.rb +52 -0
- data/lib/domain/factory/reuse.rb +53 -0
- data/lib/domain/factory/sbyc.rb +9 -0
- data/lib/domain/factory/scalar.rb +30 -0
- data/lib/domain/factory/union.rb +11 -0
- data/lib/domain/support.rb +3 -0
- data/lib/domain/support/equalizer.rb +74 -0
- data/lib/domain/support/fake_domain.rb +38 -0
- data/lib/domain/support/impl_domain.rb +17 -0
- data/lib/domain/version.rb +2 -2
- data/spec/factory/reuse_domain/test_new.rb +26 -0
- data/spec/factory/reuse_domain/test_predicate.rb +10 -0
- data/spec/factory/reuse_domain/test_recoat.rb +22 -0
- data/spec/factory/reuse_domain/test_reuse.rb +20 -0
- data/spec/factory/reuse_domain/test_values.rb +11 -0
- data/spec/factory/sbyc_domain/test_new.rb +22 -0
- data/spec/factory/sbyc_domain/test_predicate.rb +10 -0
- data/spec/factory/sbyc_domain/test_sub_domains.rb +14 -0
- data/spec/factory/sbyc_domain/test_super_domain.rb +10 -0
- data/spec/factory/sbyc_domain/test_super_domain_of.rb +18 -0
- data/spec/factory/sbyc_domain/test_triple_equal.rb +23 -0
- data/spec/factory/scalar_domain/test_component_reader.rb +16 -0
- data/spec/factory/scalar_domain/test_hash.rb +14 -0
- data/spec/factory/scalar_domain/test_to_hash.rb +14 -0
- data/spec/factory/scalar_domain/test_values.rb +11 -0
- data/spec/factory/test_sbyc.rb +16 -0
- data/spec/factory/test_scalar.rb +14 -0
- data/spec/factory/test_union.rb +17 -0
- data/spec/factory/union_domain/test_new.rb +17 -0
- data/spec/factory/union_domain/test_predicate.rb +18 -0
- data/spec/factory/union_domain/test_sub_domains.rb +10 -0
- data/spec/factory/union_domain/test_super_domain.rb +10 -0
- data/spec/factory/union_domain/test_super_domain_of.rb +18 -0
- data/spec/factory/union_domain/test_triple_equal.rb +15 -0
- data/spec/fixtures/boolean.rb +3 -0
- data/spec/fixtures/list.rb +6 -0
- data/spec/fixtures/neg_int.rb +3 -0
- data/spec/fixtures/point.rb +3 -0
- data/spec/fixtures/tuple.rb +3 -0
- data/spec/shared/a_domain_class.rb +11 -0
- data/spec/shared/a_value_object.rb +58 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/equalizer/test_new.rb +37 -0
- data/spec/support/test_equalizer.rb +95 -0
- data/spec/test_fixtures.rb +21 -0
- metadata +82 -4
data/README.md
CHANGED
@@ -1,7 +1,80 @@
|
|
1
|
-
# Domain
|
1
|
+
# Domain - Build data-types the easy way
|
2
2
|
|
3
|
-
|
3
|
+
This gem provides modules (module factories in fact) for creating domains, aka data-types, in a various common ways.
|
4
4
|
|
5
5
|
## Links
|
6
6
|
|
7
7
|
https://github.com/blambeau/domain
|
8
|
+
|
9
|
+
## Scalar domains
|
10
|
+
|
11
|
+
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.
|
12
|
+
|
13
|
+
# Immediately create a Point class, that is, an implementation of the Point domain
|
14
|
+
# This is similar to `Struct.new(:x, :y)`
|
15
|
+
Point = Domain.scalar(:x, :y)
|
16
|
+
|
17
|
+
# Another way, if you want to define operators for the Point domain
|
18
|
+
class Point
|
19
|
+
extend Domain::Scalar.new(:x, :y)
|
20
|
+
|
21
|
+
def distance
|
22
|
+
Math.sqrt(x*x + y*y)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# in either case, `==`, `eql?` and `hash` are implemented in a consistent way
|
27
|
+
Point.new(1, 2) == Point.new(1, 2)
|
28
|
+
|
29
|
+
## Reuse domains
|
30
|
+
|
31
|
+
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.
|
32
|
+
|
33
|
+
class List
|
34
|
+
extend Domain::Reuse.new(Array)
|
35
|
+
|
36
|
+
def head
|
37
|
+
# the reused instance is accessible under `reused_instance` (protected)
|
38
|
+
reused_instance.first
|
39
|
+
end
|
40
|
+
|
41
|
+
# reuse through delegation to `reused_instance`
|
42
|
+
delegate :each, :size, :empty?
|
43
|
+
|
44
|
+
# similar to `delegate` but redecorate the resulting value (will yield Lists)
|
45
|
+
reuse :map, :reject, :select
|
46
|
+
end
|
47
|
+
|
48
|
+
# The reused instance is expected to be passed at construction time
|
49
|
+
List.new [1, 2, 3]
|
50
|
+
|
51
|
+
# `==`, `eql?` and `hash` are already implemented in a consistent way
|
52
|
+
List.new([1, 2]) == List.new([1, 2])
|
53
|
+
|
54
|
+
## Union domains
|
55
|
+
|
56
|
+
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:
|
57
|
+
|
58
|
+
# Factors a Boolean domain immediately
|
59
|
+
Boolean = Domain.union(TrueClass, FalseClass)
|
60
|
+
|
61
|
+
# or use the module factory in your own class
|
62
|
+
class Boolean
|
63
|
+
extend Domain::Union.new(TrueClass, FalseClass)
|
64
|
+
end
|
65
|
+
|
66
|
+
# in either case, `===` is implemented in a consistent way
|
67
|
+
Boolean === true
|
68
|
+
Boolean === false
|
69
|
+
|
70
|
+
## Specialization by constraints
|
71
|
+
|
72
|
+
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:
|
73
|
+
|
74
|
+
# Factors the set of positive integers
|
75
|
+
PosInt = Domain.sbyc(Integer){|i| i > 0}
|
76
|
+
|
77
|
+
# or use the module factory (you'll have to extend Integer yourself for consistency)
|
78
|
+
class PosInt < Integer
|
79
|
+
extend Domain::SByC.new(Integer){|i| i > 0}
|
80
|
+
end
|
data/domain.noespec
CHANGED
data/lib/domain.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
|
+
require_relative 'domain/version'
|
2
|
+
require_relative 'domain/loader'
|
3
|
+
require_relative 'domain/support'
|
4
|
+
require_relative 'domain/api'
|
5
|
+
require_relative 'domain/factory'
|
1
6
|
#
|
2
7
|
# Provide tools for implementing domains (aka data types)
|
3
8
|
#
|
4
9
|
module Domain
|
10
|
+
include API
|
11
|
+
extend Factory
|
5
12
|
|
6
13
|
end # module Domain
|
7
|
-
require "domain/version"
|
8
|
-
require "domain/loader"
|
data/lib/domain/api.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Domain
|
2
|
+
module API
|
3
|
+
|
4
|
+
# Returns the domain predicate, nil if no such predicate
|
5
|
+
#
|
6
|
+
# @return [Proc]
|
7
|
+
# the domain predicate (possibly nil)
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
def predicate
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns the super domain of `self`.
|
15
|
+
#
|
16
|
+
# @return [Class]
|
17
|
+
# the super domain of `self` as a ruby class
|
18
|
+
#
|
19
|
+
# @api public
|
20
|
+
def super_domain
|
21
|
+
superclass
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns the sub domains of `self`.
|
25
|
+
#
|
26
|
+
# @return [Array]
|
27
|
+
# an array of sub domains (possibly empty)
|
28
|
+
#
|
29
|
+
# @api public
|
30
|
+
def sub_domains
|
31
|
+
[]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns true if this domain is a super domain of `dom`.
|
35
|
+
#
|
36
|
+
# @param [Class] dom
|
37
|
+
# a domain
|
38
|
+
#
|
39
|
+
# @return [Boolean]
|
40
|
+
# true if self is a super domain of `dom`, false otherwise
|
41
|
+
#
|
42
|
+
# @api public
|
43
|
+
def super_domain_of?(dom)
|
44
|
+
sub_domains.include?(dom)
|
45
|
+
end
|
46
|
+
|
47
|
+
end # module API
|
48
|
+
end # module Domain
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require_relative 'factory/union'
|
2
|
+
require_relative 'factory/sbyc'
|
3
|
+
require_relative 'factory/scalar'
|
4
|
+
require_relative 'factory/reuse'
|
5
|
+
module Domain
|
6
|
+
module Factory
|
7
|
+
|
8
|
+
# Creates a domain through specialization by constraint
|
9
|
+
#
|
10
|
+
# @param [Class] super_domain
|
11
|
+
# the super_domain of the factored domain
|
12
|
+
# @param [Proc] pred
|
13
|
+
# the domain predicate
|
14
|
+
#
|
15
|
+
# @return [Class]
|
16
|
+
# the created domain as a ruby Class
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
def sbyc(super_domain = Object, &pred)
|
20
|
+
Class.new(super_domain){ extend SByC.new(super_domain, pred) }
|
21
|
+
end
|
22
|
+
|
23
|
+
# Factors a union domain
|
24
|
+
#
|
25
|
+
# @param [Class] super_domain
|
26
|
+
# the super_domain of the factored domain
|
27
|
+
# @param [Array] sub_domains
|
28
|
+
# an array of sub domains for the factored domain
|
29
|
+
#
|
30
|
+
# @return [Class]
|
31
|
+
# the created domain as a ruby Class
|
32
|
+
#
|
33
|
+
# @api public
|
34
|
+
def union(*sub_domains)
|
35
|
+
Class.new{ extend Union.new(*sub_domains) }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Factors a scalar domain
|
39
|
+
#
|
40
|
+
# @param [Array] component_names
|
41
|
+
# the component names as an array of symbols
|
42
|
+
#
|
43
|
+
# @return [Class]
|
44
|
+
# the created domain as a ruby Class
|
45
|
+
#
|
46
|
+
# @api public
|
47
|
+
def scalar(*component_names)
|
48
|
+
Class.new{ extend Scalar.new(*component_names) }
|
49
|
+
end
|
50
|
+
|
51
|
+
end # module Factory
|
52
|
+
end # module Domain
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Domain
|
2
|
+
module Reuse
|
3
|
+
|
4
|
+
def self.new(reuse_domain, predicate = nil, &bl)
|
5
|
+
ImplDomain.new [ Methods, class_module(predicate || bl) ],
|
6
|
+
[ instance_module(reuse_domain) ]
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.class_module(predicate)
|
10
|
+
Module.new{ define_method(:predicate){ predicate } }
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.instance_module(reuse_domain)
|
14
|
+
Module.new{
|
15
|
+
define_method(:initialize) do |arg|
|
16
|
+
unless reuse_domain===arg
|
17
|
+
raise ArgumentError, "#{reuse_domain} expected, got `#{arg.inspect}`"
|
18
|
+
end
|
19
|
+
if self.class.predicate && !self.class.predicate.call(arg)
|
20
|
+
raise ArgumentError, "Invalid value `#{arg.inspect}` for `#{self}`"
|
21
|
+
end
|
22
|
+
@reused_instance = arg
|
23
|
+
end
|
24
|
+
define_method(:reused_instance) do
|
25
|
+
@reused_instance
|
26
|
+
end
|
27
|
+
protected :reused_instance
|
28
|
+
include Equalizer.new(:reused_instance)
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
module Methods
|
33
|
+
|
34
|
+
def reuse(*methods)
|
35
|
+
methods.each do |m|
|
36
|
+
define_method(m) do |*args, &bl|
|
37
|
+
reused_instance.send(m, *args, &bl)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def recoat(*methods)
|
43
|
+
methods.each do |m|
|
44
|
+
define_method(m) do |*args, &bl|
|
45
|
+
self.class.new reused_instance.send(m, *args, &bl)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end # module Methods
|
51
|
+
|
52
|
+
end # module Reuse
|
53
|
+
end # module Domain
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Domain
|
2
|
+
module Scalar
|
3
|
+
|
4
|
+
def self.new(*component_names)
|
5
|
+
ImplDomain.new [], instance_module(component_names)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.instance_module(component_names)
|
9
|
+
Module.new{
|
10
|
+
define_method(:initialize) do |*args|
|
11
|
+
component_names.zip(args).each do |n,arg|
|
12
|
+
instance_variable_set(:"@#{n}", arg)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
component_names.each do |n|
|
16
|
+
define_method(n) do
|
17
|
+
instance_variable_get(:"@#{n}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
define_method(:to_hash) do
|
21
|
+
component_names.each_with_object({}) do |n,h|
|
22
|
+
h[n] = instance_variable_get(:"@#{n}")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
include Equalizer.new(component_names)
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
end # class Scalar
|
30
|
+
end # module Domain
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Domain
|
2
|
+
class Equalizer < Module
|
3
|
+
|
4
|
+
# Parts of this module have been extracted from Virtus, MIT Copyright (c) 2011-2012
|
5
|
+
# Piotr Solnica.
|
6
|
+
|
7
|
+
# Creates an Equalizer instance.
|
8
|
+
#
|
9
|
+
# @param [Proc] bl
|
10
|
+
# the proc to use to extract components that participate to equality
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
def initialize(components = nil, &bl)
|
14
|
+
extractor = case components
|
15
|
+
when NilClass then bl
|
16
|
+
when Symbol then proc{ [ send(components) ] }
|
17
|
+
when Array then proc{ components.map{|n| send(n)} }
|
18
|
+
end
|
19
|
+
define_method(:equality_components, &extractor)
|
20
|
+
module_eval{ include(Methods) }
|
21
|
+
end
|
22
|
+
|
23
|
+
module Methods
|
24
|
+
|
25
|
+
# Returns a hash code for the value
|
26
|
+
#
|
27
|
+
# @return Integer
|
28
|
+
#
|
29
|
+
# @api public
|
30
|
+
def hash
|
31
|
+
equality_components.map{|c| c.hash }.reduce(self.class.hash, :^)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Compare the object with other object for equality
|
35
|
+
#
|
36
|
+
# @example
|
37
|
+
# object.eql?(other) # => true or false
|
38
|
+
#
|
39
|
+
# @param [Object] other
|
40
|
+
# the other object to compare with
|
41
|
+
#
|
42
|
+
# @return [Boolean]
|
43
|
+
#
|
44
|
+
# @api public
|
45
|
+
def eql?(other)
|
46
|
+
instance_of?(other.class) and cmp?(__method__, other)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Compare the object with other object for equivalency
|
50
|
+
#
|
51
|
+
# @example
|
52
|
+
# object == other # => true or false
|
53
|
+
#
|
54
|
+
# @param [Object] other
|
55
|
+
# the other object to compare with
|
56
|
+
#
|
57
|
+
# @return [Boolean]
|
58
|
+
#
|
59
|
+
# @api public
|
60
|
+
def ==(other)
|
61
|
+
return false unless self.class <=> other.class
|
62
|
+
cmp?(__method__, other)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def cmp?(comparator, other)
|
68
|
+
equality_components.zip(other.equality_components).
|
69
|
+
all?{|l,r| l.send(comparator, r) }
|
70
|
+
end
|
71
|
+
|
72
|
+
end # module Methods
|
73
|
+
end # class Equalizer
|
74
|
+
end # module Domain
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Domain
|
2
|
+
module FakeDomain
|
3
|
+
|
4
|
+
def self.new(super_domain, sub_domains, predicate)
|
5
|
+
Module.new{
|
6
|
+
include Domain, Methods
|
7
|
+
define_method(:super_domain){ super_domain }
|
8
|
+
define_method(:sub_domains) { sub_domains }
|
9
|
+
define_method(:predicate) { predicate }
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
module Methods
|
14
|
+
|
15
|
+
# Creates a new instance of this domain
|
16
|
+
def new(*args)
|
17
|
+
if (args.size == 1) && self===args.first
|
18
|
+
return args.first
|
19
|
+
elsif superclass.respond_to?(:new) && (superclass != Object)
|
20
|
+
return new super(*args)
|
21
|
+
end
|
22
|
+
args_error_on_new(args)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Checks if `value` belongs to this domain
|
26
|
+
def ===(value)
|
27
|
+
value.is_a?(superclass) && predicate && predicate.call(value)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def args_error_on_new(args)
|
33
|
+
raise ArgumentError, "Invalid value #{args.join(' ')} for #{self}", caller
|
34
|
+
end
|
35
|
+
|
36
|
+
end # module Methods
|
37
|
+
end # module FakeDomain
|
38
|
+
end # module Domain
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Domain
|
2
|
+
module ImplDomain
|
3
|
+
|
4
|
+
def self.new(c_methods = [], i_methods = [])
|
5
|
+
i_methods = Array(i_methods)
|
6
|
+
c_methods = Array(c_methods).unshift(Domain)
|
7
|
+
Module.new{
|
8
|
+
c_methods.each{|c_m| include(c_m)}
|
9
|
+
define_singleton_method(:extend_object) do |obj|
|
10
|
+
obj.module_eval{ i_methods.each{|i_m| include(i_m)} } if obj.is_a?(Class)
|
11
|
+
super(obj)
|
12
|
+
end
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
end # module ImplDomain
|
17
|
+
end # module Domain
|