typed_attr 0.0.1 → 0.1.0

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