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 +1 -1
- data/Guardfile +7 -0
- data/README.md +12 -2
- data/lib/typed_attr/composite_type.rb +45 -6
- data/lib/typed_attr/version.rb +1 -1
- data/lib/typed_attr.rb +43 -43
- data/spec/lib/typed_attr/composite_type_spec.rb +41 -2
- data/spec/lib/typed_attr_spec.rb +4 -4
- data/typed_attr.gemspec +1 -0
- metadata +40 -5
- checksums.yaml +0 -15
data/.rspec
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
--color
|
data/Guardfile
ADDED
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
|
-
|
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
|
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
|
51
|
+
# Constructs a type which can be A OR B.
|
50
52
|
#
|
51
53
|
# Array.of(String|Integer)
|
52
54
|
def | t
|
53
|
-
|
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
|
116
|
+
n = Numericlike === x and n < 0
|
78
117
|
end
|
79
118
|
end
|
80
119
|
|
data/lib/typed_attr/version.rb
CHANGED
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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
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
|
-
|
63
|
-
|
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 "
|
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
|
data/spec/lib/typed_attr_spec.rb
CHANGED
@@ -29,15 +29,15 @@ describe TypedAttr do
|
|
29
29
|
end
|
30
30
|
|
31
31
|
context "typed_attr" do
|
32
|
-
|
32
|
+
HashSyntax = Class.new do
|
33
33
|
typed_attr String, :a, Numeric, :b
|
34
34
|
end
|
35
|
-
|
35
|
+
ArraySyntax = Class.new do
|
36
36
|
typed_attr a: String, b: Numeric
|
37
37
|
end
|
38
38
|
|
39
|
-
[
|
40
|
-
context "#{cls}
|
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
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
|
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-
|
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:
|
159
|
+
rubygems_version: 1.8.25
|
125
160
|
signing_key:
|
126
|
-
specification_version:
|
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=
|