domain 0.0.1 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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