typed_attr 0.0.1 → 0.1.0

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/.rspec CHANGED
@@ -1 +1 @@
1
- -f d --color
1
+ --color
data/Guardfile ADDED
@@ -0,0 +1,7 @@
1
+ guard 'rspec' do
2
+ watch(%r{^spec/.+_spec\.rb$}) { "spec" }
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ watch('.rspec') { "spec" }
6
+ end
7
+
data/README.md CHANGED
@@ -21,9 +21,9 @@ Or install it yourself as:
21
21
  TypedAttr simplifies typed functional programming in Ruby.
22
22
 
23
23
  The creation of data types is critical to functional programming.
24
- Ruby does not enforce any typing of attributes or parameters.
24
+ Ruby does not enforce any typing of object attributes or method parameters.
25
25
 
26
- We introduce a class macro "typed_attr". It constructs an #initialize method
26
+ TypedAttr introduces a class macro "typed_attr". It constructs an #initialize method
27
27
  given a list of attributes and their expected types.
28
28
 
29
29
  Example:
@@ -44,11 +44,21 @@ Methods can use "typecheck" to perform checks on arguments:
44
44
  m(-1, "string") # => raise TypeError
45
45
  m(2, "string") # => "stringstring"
46
46
 
47
+ The type assertions use the #=== matching operator.
48
+
47
49
  Composite Types can be constructed to match deeper data structures:
48
50
 
49
51
  h = { "a" => 1, "b" => :symbol }
50
52
  typecheck h, Hash.of(String.with(Integer|Symbol))
51
53
 
54
+ Composite types create dynamic Modules that redefine the #=== pattern matching operator.
55
+ Thus composite types can be used in "case when" clauses:
56
+
57
+ case h
58
+ when Hash.of(String.with(Users)) ...
59
+ when Hash.of(Symbol.with(Object)) ...
60
+ end
61
+
52
62
  ## Contributing
53
63
 
54
64
  1. Fork it
@@ -1,9 +1,11 @@
1
1
  class Module
2
2
  class CompositeType < self
3
- def initialize a, b
3
+ def initialize a, b = nil
4
4
  @a = a
5
5
  @b = b
6
6
  end
7
+ def _a; @a; end
8
+ def _b; @b; end
7
9
  def to_s
8
10
  @to_s ||= "#{@a}.#{op}(#{@b})".freeze
9
11
  end
@@ -37,20 +39,57 @@ class Module
37
39
  PairType.new(self, t)
38
40
  end
39
41
 
40
- class AlternateType < CompositeType
42
+ class DisjunctiveType < CompositeType
41
43
  def === x
42
44
  @a === x or @b === x
43
45
  end
44
46
  def to_s
45
- @to_s ||= "#{@a}|#{@b}".freeze
47
+ @to_s ||= "(#{@a}|#{@b})".freeze
46
48
  end
47
49
  end
48
50
 
49
- # Constructs a type of Pairs.
51
+ # Constructs a type which can be A OR B.
50
52
  #
51
53
  # Array.of(String|Integer)
52
54
  def | t
53
- AlternateType.new(self, t)
55
+ DisjunctiveType.new(self, t)
56
+ end
57
+
58
+ class ConjunctiveType < CompositeType
59
+ def === x
60
+ @a === x and @b === x
61
+ end
62
+ def to_s
63
+ @to_s ||= "(#{@a}&#{@b})".freeze
64
+ end
65
+ end
66
+
67
+ # Constructs a type which must be A AND B.
68
+ #
69
+ # Array.of(Positive&Integer)
70
+ def & t
71
+ ConjunctiveType.new(self, t)
72
+ end
73
+
74
+ class NegativeType < CompositeType
75
+ def === x
76
+ ! @a === x
77
+ end
78
+ def to_s
79
+ @to_s ||= "(~#{@a})".freeze
80
+ end
81
+ end
82
+
83
+ # Constructs a type which must not be A.
84
+ #
85
+ # Array.of(~NilClass)
86
+ def ~@
87
+ case self
88
+ when NegativeType
89
+ self.a
90
+ else
91
+ NegativeType.new(self)
92
+ end
54
93
  end
55
94
  end
56
95
 
@@ -74,7 +113,7 @@ end
74
113
 
75
114
  module Negative
76
115
  def self.=== x
77
- n = Numericlike == x and n < 0
116
+ n = Numericlike === x and n < 0
78
117
  end
79
118
  end
80
119
 
@@ -1,3 +1,3 @@
1
1
  module TypedAttr
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/typed_attr.rb CHANGED
@@ -1,55 +1,56 @@
1
+ require 'typed_attr/version'
1
2
  require 'typed_attr/enumerable' # map_with_index
2
3
 
3
- # Typed Attributes.
4
- module TypedAttr
5
- def self.included target
6
- super
7
- target.extend(ModuleMethods)
4
+ # Typed Attributes.
5
+ module TypedAttr
6
+ def self.included target
7
+ super
8
+ target.extend(ModuleMethods)
9
+ end
10
+
11
+ # typecheck value, pattern, ...
12
+ #
13
+ # Check that every pattern === value.
14
+ # Raise TypeError if any do not.
15
+ # Returns value.
16
+ def typecheck value, *checks
17
+ unless checks.all? { | check | check === value }
18
+ raise TypeError, "expected (#{checks * ', '}), given #{value}"
8
19
  end
20
+ value
21
+ end
9
22
 
10
- # typecheck value, pattern, ...
11
- #
12
- # Check that every pattern === value.
13
- # Raise TypeError if any do not.
14
- # Returns value.
15
- def typecheck value, *checks
16
- unless checks.all? { | check | check === value }
17
- raise TypeError, "expected (#{checks * ', '}), given #{value}"
18
- end
19
- value
23
+ module ModuleMethods
24
+ OPTIONS = { }
25
+ def typed_attr_option opts
26
+ OPTIONS.update(opts)
20
27
  end
21
28
 
22
- module ModuleMethods
23
- OPTIONS = { }
24
- def typed_attr_option opts
25
- OPTIONS.update(opts)
29
+ # typed_attr name: Type, ...
30
+ # typed_attr Type, :name, ...
31
+ #
32
+ # Generates an initialize method that will accept each :name as a typechecked positional argument.
33
+ # Unspecified arguments are undefined and not typechecked.
34
+ # Additional arguments are ignored.
35
+ def typed_attr *types_and_names
36
+ if h = types_and_names.first and Hash === h and types_and_names.size == 1
37
+ names = h.keys
38
+ name_to_type = h
39
+ else
40
+ name_to_type = Hash[*types_and_names.reverse]
41
+ names =
42
+ (0 .. types_and_names.size).to_a.
43
+ keep_if(&:odd?).
44
+ map { | i | types_and_names[i] }
26
45
  end
27
-
28
- # typed_attr name: Type, ...
29
- # typed_attr Type, :name, ...
30
- #
31
- # Generates an initialize method that will accept each :name as a typechecked positional argument.
32
- # Unspecified arguments are undefined and not typechecked.
33
- # Additional arguments are ignored.
34
- def typed_attr *types_and_names
35
- if h = types_and_names.first and Hash === h and types_and_names.size == 1
36
- names = h.keys
37
- name_to_type = h
38
- else
39
- name_to_type = Hash[*types_and_names.reverse]
40
- names =
41
- (0 .. types_and_names.size).to_a.
42
- keep_if(&:odd?).
43
- map { | i | types_and_names[i] }
44
- end
45
- expr = <<"END"
46
+ expr = <<"END"
46
47
  def initialize *__args
47
48
  initialize_typed_attrs *__args
48
49
  end
49
50
 
50
51
  def initialize_typed_attrs *__args
51
52
  #{names.map_with_index do | name, i |
52
- "@#{name} = __args[#{i}] if __args.size > #{i}"
53
+ "@#{name} = __args[#{i}] if __args.size > #{i}"
53
54
  end * "\n "}
54
55
  #{"binding.pry if #{OPTIONS[:pry_if] || true}" if OPTIONS[:pry]}
55
56
  #{names.map_with_index do | name, i |
@@ -59,12 +60,11 @@ def initialize_typed_attrs *__args
59
60
  end
60
61
  attr_reader #{names.map(&:to_sym).map(&:inspect) * ', '}
61
62
  END
62
- $stderr.puts "#{self}\n#{expr}" if OPTIONS[:debug]
63
- class_eval expr
64
- end
63
+ $stderr.puts "#{self}\n#{expr}" if OPTIONS[:debug]
64
+ class_eval expr
65
65
  end
66
66
  end
67
-
67
+ end
68
68
 
69
69
  # Pollute Object.
70
70
  Object.send(:include, TypedAttr)
@@ -74,7 +74,7 @@ describe Class::CompositeType do
74
74
  end
75
75
  end
76
76
 
77
- context "AlternateType" do
77
+ context "DisjunctiveType" do
78
78
  it "should not fail when empty" do
79
79
  v = [ ]
80
80
  (Array.of(String|Integer) === v).should === true
@@ -91,6 +91,28 @@ describe Class::CompositeType do
91
91
  end
92
92
  end
93
93
 
94
+ context "ConjunctiveType" do
95
+ it "should not fail when empty" do
96
+ v = [ ]
97
+ (Array.of(Positive & Integer) === v).should === true
98
+ end
99
+
100
+ it "should not fail" do
101
+ v = [ 1, 2 ]
102
+ (Array.of(Positive & Integer) === v).should === true
103
+ end
104
+
105
+ it "should fail" do
106
+ v = [ 0, 1 ]
107
+ (Array.of(Positive & Integer) === v).should === false
108
+ end
109
+
110
+ it "should fail" do
111
+ v = [ 1, :symbol ]
112
+ (Array.of(Positive & Integer) === v).should === false
113
+ end
114
+ end
115
+
94
116
  context "Positive" do
95
117
  it "should be true for Numeric" do
96
118
  v = 1234
@@ -108,6 +130,23 @@ describe Class::CompositeType do
108
130
  end
109
131
  end
110
132
 
133
+ context "Negative" do
134
+ it "should be true for negative Numeric" do
135
+ v = -1234
136
+ (Negative === v).should == true
137
+ end
138
+
139
+ it "should be false for positive" do
140
+ v = 1234
141
+ (Negative === v).should be_false
142
+ end
143
+
144
+ it "should be false for non-Numeric" do
145
+ v = "a String"
146
+ (Negative === v).should be_false
147
+ end
148
+ end
149
+
111
150
  context "misc" do
112
151
  it "example 1" do
113
152
  h = { "a" => 1, "b" => :symbol }
@@ -120,7 +159,7 @@ describe Class::CompositeType do
120
159
  end
121
160
 
122
161
  it "should handle to_s" do
123
- Hash.of(String.with(Integer|Symbol)).to_s.should == "Hash.of(String.with(Integer|Symbol))"
162
+ Hash.of(String.with(Integer|Symbol)).to_s.should == "Hash.of(String.with((Integer|Symbol)))"
124
163
  end
125
164
 
126
165
  end
@@ -29,15 +29,15 @@ describe TypedAttr do
29
29
  end
30
30
 
31
31
  context "typed_attr" do
32
- Old = Class.new do
32
+ HashSyntax = Class.new do
33
33
  typed_attr String, :a, Numeric, :b
34
34
  end
35
- New = Class.new do
35
+ ArraySyntax = Class.new do
36
36
  typed_attr a: String, b: Numeric
37
37
  end
38
38
 
39
- [ Old, New ].each do | cls |
40
- context "#{cls} syntax" do
39
+ [ HashSyntax, ArraySyntax ].each do | cls |
40
+ context "#{cls}" do
41
41
  it "should handle ()" do
42
42
  obj = cls.new
43
43
  obj.a.should == nil
data/typed_attr.gemspec CHANGED
@@ -23,4 +23,5 @@ Gem::Specification.new do |spec|
23
23
  spec.add_development_dependency "rspec", "~> 2.14"
24
24
  spec.add_development_dependency "simplecov", "~> 0.8"
25
25
  spec.add_development_dependency "pry", "~> 0.9"
26
+ spec.add_development_dependency "guard-rspec", "~> 4.0"
26
27
  end
metadata CHANGED
@@ -1,18 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typed_attr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
+ prerelease:
5
6
  platform: ruby
6
7
  authors:
7
8
  - Kurt Stephens
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2013-11-14 00:00:00.000000000 Z
12
+ date: 2013-11-15 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: bundler
15
16
  requirement: !ruby/object:Gem::Requirement
17
+ none: false
16
18
  requirements:
17
19
  - - ~>
18
20
  - !ruby/object:Gem::Version
@@ -20,6 +22,7 @@ dependencies:
20
22
  type: :development
21
23
  prerelease: false
22
24
  version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
23
26
  requirements:
24
27
  - - ~>
25
28
  - !ruby/object:Gem::Version
@@ -27,6 +30,7 @@ dependencies:
27
30
  - !ruby/object:Gem::Dependency
28
31
  name: rake
29
32
  requirement: !ruby/object:Gem::Requirement
33
+ none: false
30
34
  requirements:
31
35
  - - ! '>='
32
36
  - !ruby/object:Gem::Version
@@ -34,6 +38,7 @@ dependencies:
34
38
  type: :development
35
39
  prerelease: false
36
40
  version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
37
42
  requirements:
38
43
  - - ! '>='
39
44
  - !ruby/object:Gem::Version
@@ -41,6 +46,7 @@ dependencies:
41
46
  - !ruby/object:Gem::Dependency
42
47
  name: rspec
43
48
  requirement: !ruby/object:Gem::Requirement
49
+ none: false
44
50
  requirements:
45
51
  - - ~>
46
52
  - !ruby/object:Gem::Version
@@ -48,6 +54,7 @@ dependencies:
48
54
  type: :development
49
55
  prerelease: false
50
56
  version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
51
58
  requirements:
52
59
  - - ~>
53
60
  - !ruby/object:Gem::Version
@@ -55,6 +62,7 @@ dependencies:
55
62
  - !ruby/object:Gem::Dependency
56
63
  name: simplecov
57
64
  requirement: !ruby/object:Gem::Requirement
65
+ none: false
58
66
  requirements:
59
67
  - - ~>
60
68
  - !ruby/object:Gem::Version
@@ -62,6 +70,7 @@ dependencies:
62
70
  type: :development
63
71
  prerelease: false
64
72
  version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
65
74
  requirements:
66
75
  - - ~>
67
76
  - !ruby/object:Gem::Version
@@ -69,6 +78,7 @@ dependencies:
69
78
  - !ruby/object:Gem::Dependency
70
79
  name: pry
71
80
  requirement: !ruby/object:Gem::Requirement
81
+ none: false
72
82
  requirements:
73
83
  - - ~>
74
84
  - !ruby/object:Gem::Version
@@ -76,10 +86,27 @@ dependencies:
76
86
  type: :development
77
87
  prerelease: false
78
88
  version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
79
90
  requirements:
80
91
  - - ~>
81
92
  - !ruby/object:Gem::Version
82
93
  version: '0.9'
94
+ - !ruby/object:Gem::Dependency
95
+ name: guard-rspec
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: '4.0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: '4.0'
83
110
  description: Typed Attributes and Composite Types for Functional Programming in Ruby
84
111
  email:
85
112
  - ks.github@kurtstephens.com
@@ -90,6 +117,7 @@ files:
90
117
  - .gitignore
91
118
  - .rspec
92
119
  - Gemfile
120
+ - Guardfile
93
121
  - LICENSE.txt
94
122
  - README.md
95
123
  - Rakefile
@@ -104,26 +132,33 @@ files:
104
132
  homepage: https://github.com/kstephens/typed_attr
105
133
  licenses:
106
134
  - MIT
107
- metadata: {}
108
135
  post_install_message:
109
136
  rdoc_options: []
110
137
  require_paths:
111
138
  - lib
112
139
  required_ruby_version: !ruby/object:Gem::Requirement
140
+ none: false
113
141
  requirements:
114
142
  - - ! '>='
115
143
  - !ruby/object:Gem::Version
116
144
  version: '0'
145
+ segments:
146
+ - 0
147
+ hash: 891152326626141261
117
148
  required_rubygems_version: !ruby/object:Gem::Requirement
149
+ none: false
118
150
  requirements:
119
151
  - - ! '>='
120
152
  - !ruby/object:Gem::Version
121
153
  version: '0'
154
+ segments:
155
+ - 0
156
+ hash: 891152326626141261
122
157
  requirements: []
123
158
  rubyforge_project:
124
- rubygems_version: 2.1.10
159
+ rubygems_version: 1.8.25
125
160
  signing_key:
126
- specification_version: 4
161
+ specification_version: 3
127
162
  summary: ! 'typed_attr name: String, ...'
128
163
  test_files:
129
164
  - spec/lib/typed_attr/composite_type_spec.rb
checksums.yaml DELETED
@@ -1,15 +0,0 @@
1
- ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- ZTlhNzIwY2U5N2E1ZDMyNzYwOWJlYWU3ODY5NDJiNjY4NWJhMjdjZA==
5
- data.tar.gz: !binary |-
6
- MGNiZTk2YTJhNzIyZjA1NGUzY2NkMGFhYjFmOTY4M2I1Yjk4ODFlMQ==
7
- SHA512:
8
- metadata.gz: !binary |-
9
- NTZiZWIyZmM4ZjZjZGNkMjMxZTdlODMyNTQwMzFhMjU1NDNhMDQwNDAxNmE0
10
- MGU2MjU0ZWY5YjgwMzQyMjg3MTIxNDU5NTlmMTQzZWViOWE5YTAyOWNkOWYx
11
- ODM4NTE3NjMzYWQ5MTg1MmM3ZTA1ODM0ZTk4NjRhNTY2ZDUyNGQ=
12
- data.tar.gz: !binary |-
13
- ODk0MTI3NWM0N2JlNGNiNDgzYWZkOWM1MTc3MmNhNGMxMTYwOTVjODQzN2Mx
14
- OTNkYTdjM2FkMTJlZTU3ZmZhNzAyZjQ5M2ZhNzY1NjRmMGVjZDhkZjhlNDRh
15
- OGJlNjY2NDQ2YWMzMDVmNDlkZTFjYTRiYjA4NWUxYzZkZGZhNjk=