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.
Files changed (50) hide show
  1. data/README.md +75 -2
  2. data/domain.noespec +1 -1
  3. data/lib/domain.rb +7 -2
  4. data/lib/domain/api.rb +48 -0
  5. data/lib/domain/factory.rb +52 -0
  6. data/lib/domain/factory/reuse.rb +53 -0
  7. data/lib/domain/factory/sbyc.rb +9 -0
  8. data/lib/domain/factory/scalar.rb +30 -0
  9. data/lib/domain/factory/union.rb +11 -0
  10. data/lib/domain/support.rb +3 -0
  11. data/lib/domain/support/equalizer.rb +74 -0
  12. data/lib/domain/support/fake_domain.rb +38 -0
  13. data/lib/domain/support/impl_domain.rb +17 -0
  14. data/lib/domain/version.rb +2 -2
  15. data/spec/factory/reuse_domain/test_new.rb +26 -0
  16. data/spec/factory/reuse_domain/test_predicate.rb +10 -0
  17. data/spec/factory/reuse_domain/test_recoat.rb +22 -0
  18. data/spec/factory/reuse_domain/test_reuse.rb +20 -0
  19. data/spec/factory/reuse_domain/test_values.rb +11 -0
  20. data/spec/factory/sbyc_domain/test_new.rb +22 -0
  21. data/spec/factory/sbyc_domain/test_predicate.rb +10 -0
  22. data/spec/factory/sbyc_domain/test_sub_domains.rb +14 -0
  23. data/spec/factory/sbyc_domain/test_super_domain.rb +10 -0
  24. data/spec/factory/sbyc_domain/test_super_domain_of.rb +18 -0
  25. data/spec/factory/sbyc_domain/test_triple_equal.rb +23 -0
  26. data/spec/factory/scalar_domain/test_component_reader.rb +16 -0
  27. data/spec/factory/scalar_domain/test_hash.rb +14 -0
  28. data/spec/factory/scalar_domain/test_to_hash.rb +14 -0
  29. data/spec/factory/scalar_domain/test_values.rb +11 -0
  30. data/spec/factory/test_sbyc.rb +16 -0
  31. data/spec/factory/test_scalar.rb +14 -0
  32. data/spec/factory/test_union.rb +17 -0
  33. data/spec/factory/union_domain/test_new.rb +17 -0
  34. data/spec/factory/union_domain/test_predicate.rb +18 -0
  35. data/spec/factory/union_domain/test_sub_domains.rb +10 -0
  36. data/spec/factory/union_domain/test_super_domain.rb +10 -0
  37. data/spec/factory/union_domain/test_super_domain_of.rb +18 -0
  38. data/spec/factory/union_domain/test_triple_equal.rb +15 -0
  39. data/spec/fixtures/boolean.rb +3 -0
  40. data/spec/fixtures/list.rb +6 -0
  41. data/spec/fixtures/neg_int.rb +3 -0
  42. data/spec/fixtures/point.rb +3 -0
  43. data/spec/fixtures/tuple.rb +3 -0
  44. data/spec/shared/a_domain_class.rb +11 -0
  45. data/spec/shared/a_value_object.rb +58 -0
  46. data/spec/spec_helper.rb +17 -0
  47. data/spec/support/equalizer/test_new.rb +37 -0
  48. data/spec/support/test_equalizer.rb +95 -0
  49. data/spec/test_fixtures.rb +21 -0
  50. metadata +82 -4
data/README.md CHANGED
@@ -1,7 +1,80 @@
1
- # Domain
1
+ # Domain - Build data-types the easy way
2
2
 
3
- Provide tools for implementing domains (aka data types)
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
@@ -7,7 +7,7 @@ variables:
7
7
  upper:
8
8
  Domain
9
9
  version:
10
- 0.0.1
10
+ 1.0.0.rc1
11
11
  summary: |-
12
12
  Provide tools for implementing domains (aka data types)
13
13
  description: |-
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,9 @@
1
+ module Domain
2
+ module SByC
3
+
4
+ def self.new(super_domain = Object, predicate = nil, &bl)
5
+ FakeDomain.new(super_domain, [], predicate || bl)
6
+ end
7
+
8
+ end # module SByC
9
+ 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,11 @@
1
+ module Domain
2
+ module Union
3
+
4
+ def self.new(*sub_domains)
5
+ FakeDomain.new(Object, sub_domains, lambda{|t| sub_domains.any?{|d| d===t } })
6
+ end
7
+
8
+ end # class Union
9
+ end # module Domain
10
+
11
+
@@ -0,0 +1,3 @@
1
+ require_relative 'support/equalizer'
2
+ require_relative 'support/fake_domain'
3
+ require_relative 'support/impl_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