functo 0.1.3 → 0.1.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 11f0d93203f4181a8d9783fefd67aba0ae43856d
4
- data.tar.gz: d3c4fc5ced337e6cc26cbd5b664541037692f6e6
3
+ metadata.gz: 1aacfe82c8c6c9ae6d012f3d8e6fdf0a76e95624
4
+ data.tar.gz: b214a8b047a2e3dfcfa825f407a59c85f8815706
5
5
  SHA512:
6
- metadata.gz: c3c28d9beeef0a14e599e2faaf53233a84a838858c250bbc1a7b8be5d1e085b5efcb0e572164cd3be227fa80a89999d985577fe1155b2417c486b074b220ca70
7
- data.tar.gz: e1541a748c161c5d2eacf7dd093fcb83ed171bead03d7eea0318f21f250a11e54272fca809e3f35389e1d103c8a5518a6476c9cbd031726f4f7ac88768f9c9b3
6
+ metadata.gz: f913c2170fe113976ee62b1e3c4c3d89a6b9293db56c8612073b7e3f8d490bc859465ff11e8362e6380cd5ad14798ae06679211c6ffe65c5f418600cb85bd611
7
+ data.tar.gz: 6ef2cb2b623d76ce701fdbaeff494fb0966aced469551f4cd5bf0c4bd98c08fa4a12efb4c3c0085bdaefd5b6209f264a909206fdd7088dda76c16af92b1fa2bb
data/.gitignore CHANGED
@@ -7,3 +7,4 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ /README.html
data/README.md CHANGED
@@ -1,118 +1,53 @@
1
1
  # Functo
2
2
 
3
- Functo is a dynamic module for composable method objects in ruby.
3
+ Composable method objects in ruby.
4
4
 
5
- It turns this:
6
-
7
- ```ruby
8
- class AddsTwo
9
- def self.call(*args)
10
- new(*args).add
11
- end
12
-
13
- attr_reader :number
14
- protected :number
15
-
16
- def initialize(number)
17
- @number = number
18
- end
19
-
20
- def add
21
- number + 2
22
- end
23
- end
24
- ```
25
-
26
- in to this:
5
+ ## Usage
27
6
 
28
7
  ```ruby
29
- class AddsTwo
8
+ class AddsOne
30
9
  include Functo.call :add, :number
31
10
 
32
11
  def add
33
- number + 2
12
+ number + 1
34
13
  end
35
14
  end
36
- ```
37
-
38
- ## Installation
39
15
 
40
- Add this line to your application's Gemfile:
16
+ AddsOne[1]
17
+ # => 2
41
18
 
42
- ```ruby
43
- gem 'functo'
44
- ```
45
-
46
- And then execute:
47
-
48
- $ bundle
49
-
50
- Or install it yourself as:
51
-
52
- $ gem install functo
53
-
54
- ## Usage
55
-
56
- Functo objects can take up to three arguments.
57
-
58
- ```ruby
59
- class Multiply
60
- include Functo.call :multiply, :first, :second, :third
19
+ class Multiplies
20
+ include Functo.call :multiply, :foo, :bar
61
21
 
62
22
  def multiply
63
- first * second * third
64
- end
65
- end
66
-
67
- Multiply.call(10, 20, 30)
68
- # => 6000
69
-
70
- class Divide
71
- include Functo.call :multiply, :first, :second, :third, :fourth
72
-
73
- def divide
74
- first / second / third / fourth
23
+ foo * bar
75
24
  end
76
25
  end
77
- # => ArgumentError: given 4 arguments when only 3 are allowed
78
- ```
79
-
80
- If you find yourself needing more you should consider composing method objects or encapsulating some of your arguments in another object.
81
26
 
82
- You can use square brackets to call Functo objects:
83
-
84
- ```ruby
85
- AddsTwo[3]
86
- # => 5
27
+ Multiplies[2, 3]
28
+ # => 6
87
29
  ```
88
30
 
89
- and they can be used in blocks:
31
+ Functo objects can be used in place of a `Proc`.
90
32
 
91
33
  ```ruby
92
- [1, 2, 3].map(&AddsTwo)
93
- # => [3, 4, 5]
34
+ [1, 2, 3].map(&AddsOne)
35
+ # => [2, 3, 4]
94
36
  ```
95
37
 
96
38
  ### Composition
97
39
 
98
- Functo objects can be composed using `compose` or the turbo operator `>>`:
99
-
100
40
  ```ruby
101
- AddMulti = AddsTwo.compose(MultipliesThree)
41
+ MultipliesAddsOne = Multiplies >> AddsOne
102
42
 
103
- AddMulti.call(3)
104
- # => 15
105
-
106
- MultiAdd = MultipliesThree >> AddsTwo
107
-
108
- MultiAdd[3]
109
- # => 11
43
+ MultipliesAddsOne[2, 3]
44
+ # => 7
110
45
  ```
111
46
 
112
- The difference between the two is that the turbo operator will splat arrays passed between the composed objects but `compose` will not.
47
+ `>>` splats intermediate results. Use `>` to compose without splatting intermediate results.
113
48
 
114
49
  ```ruby
115
- class SplitDigits
50
+ class SplitsDigits
116
51
  include Functo.call :split, :number
117
52
 
118
53
  def split
@@ -120,58 +55,51 @@ class SplitDigits
120
55
  end
121
56
  end
122
57
 
123
- class Sum
124
- include Functo.call :sum, :first, :second, :third
58
+ class Sums
59
+ include Functo.call :sum, :arr
125
60
 
126
61
  def sum
127
- first + second + third
62
+ arr.reduce(:+)
128
63
  end
129
64
  end
130
65
 
131
- SumDigits = SplitDigits >> Sum
132
-
133
- SumDigits[123]
134
- # => 6
66
+ SumsDigits = SplitsDigits >> Sums
67
+ SumsDigits[1066]
68
+ # => ArgumentError: wrong number of arguments (4 for 1)
135
69
 
136
- SumDigits2 = SplitDigits.compose(Sum)
137
-
138
- SumDigits2[123]
139
- # => ArgumentError: wrong number of arguments (given 1, expected 3)
70
+ SumsDigits2 = SplitsDigits > Sums
71
+ SumsDigits2[1066]
72
+ # => 13
140
73
  ```
141
74
 
142
- ### Filters
143
-
144
- Filters can be passed to the Functo constructor, for example to implement types or coercion. A filter can be anything which responds to `[]`.
75
+ Any object that responds to `call` can be made composable.
145
76
 
146
77
  ```ruby
147
- class ValidatesNumeric
148
- include Functo.call :validate, :number
149
-
150
- ValidationError = Class.new(StandardError)
78
+ SquareRoots = Functo.wrap ->(n) { Math.sqrt(n) }
151
79
 
152
- def validate
153
- raise ValidationError unless number.is_a?(Numeric)
80
+ SquareRootsAddsOne = SquareRoots >> AddsOne
81
+ SquareRootsAddsOne[16]
82
+ # => 5.0
83
+ ```
154
84
 
155
- number
156
- end
157
- end
85
+ ### Filters
158
86
 
159
- class AddsThree
160
- include Functo.call :add, number: ValidatesNumeric
87
+ ```ruby
88
+ class DividesTwo
89
+ include Functo.call :divide, number: ->(n) { Float(n) }
161
90
 
162
- def add
163
- number + 2
91
+ def divide
92
+ 2 / number
164
93
  end
165
94
  end
166
95
 
167
- AddsThree[10]
168
- # => 13
169
-
170
- AddsThree['10']
171
- # => ValidationError
96
+ DividesTwo['4']
97
+ # => 0.5
172
98
  ```
173
99
 
174
- You could use the `dry-types` gem for example.
100
+ A filter can be any object that responds to `call` or `[]`.
101
+
102
+ For example using [dry-types](https://github.com/dry-rb/dry-types).
175
103
 
176
104
  ```ruby
177
105
  require 'dry-types'
@@ -180,30 +108,28 @@ module Types
180
108
  include Dry::Types.module
181
109
  end
182
110
 
183
- class AddsFour
184
- include Functo.call :add, number: Types::Strict::Int
111
+ class Squares
112
+ include Functo.call :square, number: Types::Strict::Int
185
113
 
186
- def add
187
- number + 4
114
+ def square
115
+ number**2
188
116
  end
189
117
  end
190
118
 
191
- AddsFour[4]
192
- # => 4
193
-
194
- AddsFour['4']
195
- # => Dry::Types::ConstraintError
119
+ Squares[4]
120
+ # => 16
196
121
 
122
+ Squares['4']
123
+ # => Dry::Types::ConstraintError: "4" violates constraints
197
124
  ```
198
125
 
199
- If you have multiple arguments and only want one of them to have a filter, you can use `Functo.pass` for a filter that just returns its input.
200
-
201
126
  ## Acknowledgements
202
127
 
203
- Functo was inspired by these gems:
128
+ Functo was inspired by:
204
129
 
205
130
  * [concord](https://github.com/mbj/concord) by mbj
206
131
  * [procto](https://github.com/snusnu/procto) by snusnu
132
+ * [dry-pipeline](https://github.com/dry-rb/dry-pipeline)
207
133
 
208
134
  ## Contributing
209
135
 
data/lib/functo.rb CHANGED
@@ -1,52 +1,66 @@
1
1
  require "functo/version"
2
+ require "functo/compose"
2
3
 
3
4
  class Functo < Module
4
- MAX_ARGUMENTS = 3
5
+ MAX_ATTRIBUTES = 3
5
6
  PASS = '__FUNCTO_PASS__'.freeze
6
7
 
7
- private_class_method :new
8
+ class << self
9
+ private :new
8
10
 
9
- def self.wrap(obj)
10
- klass = Class.new
11
+ def define_method_object(&block)
12
+ Class.new.tap do |klass|
13
+ klass.define_singleton_method(:call, &block)
14
+ klass.extend(Functo::Compose)
15
+ end
16
+ end
11
17
 
12
- klass.define_singleton_method :call do |*args|
13
- obj.call(*args)
18
+ def wrap(obj)
19
+ define_method_object do |*args|
20
+ obj.call(*args)
21
+ end
14
22
  end
15
23
 
16
- klass.extend(ClassMethods)
17
- klass
18
- end
24
+ def pass
25
+ PASS
26
+ end
19
27
 
20
- def self.pass
21
- PASS
22
- end
28
+ def call(*args)
29
+ new(*parse_args(args))
30
+ end
23
31
 
24
- def self.call(*names)
25
- output = names.shift
32
+ private
26
33
 
27
- if names.first.is_a?(Hash)
28
- inputs = names.first
29
- filters = inputs.values
30
- names = inputs.keys
31
- else
32
- filters = [pass] * names.length
33
- end
34
+ def parse_args(args)
35
+ function, *inputs = *args
34
36
 
35
- if names.length > MAX_ARGUMENTS
36
- raise ArgumentError.new("given #{names.length} arguments when only #{MAX_ARGUMENTS} are allowed")
37
- end
37
+ if inputs.first.is_a?(Hash)
38
+ inputs = inputs.first
39
+
40
+ attributes = inputs.keys
41
+ filters = inputs.values
42
+ else
43
+ attributes = inputs
44
+ filters = [pass] * inputs.length
45
+ end
38
46
 
39
- new(names, filters, output)
47
+ if attributes.length > MAX_ATTRIBUTES
48
+ raise ArgumentError.new("given #{attributes.length} attributes when only #{MAX_ATTRIBUTES} are allowed")
49
+ end
50
+
51
+ [attributes, function, filters]
52
+ end
40
53
  end
41
54
 
42
55
  private
43
56
 
44
- def initialize(inputs, filters, output)
45
- @inputs = inputs
57
+ def initialize(attributes, function, filters)
58
+ @attributes = attributes
59
+ @function = function
46
60
  @filters = filters
47
- @output = output
48
- @inputs_module = Module.new
49
- @output_module = Module.new
61
+
62
+ @attributes_module = Module.new
63
+ @function_module = Module.new
50
64
 
51
65
  define_initialize
52
66
  define_readers
@@ -54,27 +68,30 @@ class Functo < Module
54
68
  end
55
69
 
56
70
  def included(host)
57
- host.include(@inputs_module)
58
- host.extend(@output_module)
71
+ host.include(@attributes_module)
72
+ host.extend(@function_module)
59
73
 
60
- host.extend(ClassMethods)
74
+ host.extend(Functo::Compose)
61
75
  end
62
76
 
63
77
  def define_initialize
64
- ivars = @inputs.map { |name| "@#{name}" }
65
- filter_proc = method(:apply_filters).to_proc
66
- size = @inputs.size
78
+ ivars = @attributes.map { |name| "@#{name}" }
79
+ size = @attributes.size
80
+ filter = method(:apply_filters).to_proc
67
81
 
68
- @inputs_module.class_eval do
82
+ @attributes_module.class_eval do
69
83
  define_method :initialize do |*args|
70
84
  args_size = args.size
71
85
 
72
86
  if args_size != size
73
- fail ArgumentError, "wrong number of arguments (#{args_size} for #{size})"
87
+ message = "wrong number of arguments (#{args_size} for #{size})"
88
+
89
+ raise ArgumentError.new(message)
74
90
  end
75
91
 
76
- args = filter_proc.(args)
77
- ivars.zip(args) { |ivar, arg| instance_variable_set(ivar, arg) }
92
+ ivars.zip(filter.call(args)) do |ivar, arg|
93
+ instance_variable_set(ivar, arg)
94
+ end
78
95
  end
79
96
 
80
97
  private :initialize
@@ -82,27 +99,27 @@ class Functo < Module
82
99
  end
83
100
 
84
101
  def define_readers
85
- attribute_names = @inputs
102
+ attributes = @attributes
86
103
 
87
- @inputs_module.class_eval do
88
- attr_reader(*attribute_names)
89
- protected(*attribute_names)
104
+ @attributes_module.class_eval do
105
+ attr_reader(*attributes)
106
+ protected(*attributes)
90
107
  end
91
108
  end
92
109
 
93
110
  def define_call
94
- call_method = @output
111
+ function = @function
95
112
 
96
- @output_module.class_eval do
113
+ @function_module.class_eval do
97
114
  define_method :call do |*args|
98
- new(*args).public_send(call_method)
115
+ new(*args).public_send(function)
99
116
  end
100
117
  end
101
118
  end
102
119
 
103
120
  def apply_filters(args)
104
121
  args.zip(@filters).map do |arg, filter|
105
- if filter === Functo.pass
122
+ if filter.equal?(Functo.pass)
106
123
  arg
107
124
  elsif filter.respond_to?(:[])
108
125
  filter[arg]
@@ -114,35 +131,4 @@ class Functo < Module
114
131
  end
115
132
  end
116
133
 
117
- module ClassMethods
118
- def [](*args)
119
- call(*args)
120
- end
121
-
122
- def to_proc
123
- public_method(:call).to_proc
124
- end
125
-
126
- def compose(outer, splat: false)
127
- inner = self
128
- klass = Class.new
129
-
130
- klass.define_singleton_method :call do |*args|
131
- if splat
132
- outer.call(*inner.call(*args))
133
- else
134
- outer.call(inner.call(*args))
135
- end
136
- end
137
-
138
- klass.extend(ClassMethods)
139
- klass
140
- end
141
-
142
- def >>(outer)
143
- compose(outer, splat: true)
144
- end
145
- end
146
- private_constant(:ClassMethods)
147
-
148
134
  end
@@ -0,0 +1,29 @@
1
+ module Functo::Compose
2
+ def [](*args)
3
+ call(*args)
4
+ end
5
+
6
+ def to_proc
7
+ public_method(:call).to_proc
8
+ end
9
+
10
+ def compose(outer, splat: false)
11
+ inner = self
12
+
13
+ Functo.define_method_object do |*args|
14
+ if splat
15
+ outer.call(*inner.call(*args))
16
+ else
17
+ outer.call(inner.call(*args))
18
+ end
19
+ end
20
+ end
21
+
22
+ def >(outer)
23
+ compose(outer, splat: false)
24
+ end
25
+
26
+ def >>(outer)
27
+ compose(outer, splat: true)
28
+ end
29
+ end
@@ -1,3 +1,3 @@
1
1
  class Functo < Module
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.4"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: functo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Scully
@@ -70,6 +70,7 @@ files:
70
70
  - bin/setup
71
71
  - functo.gemspec
72
72
  - lib/functo.rb
73
+ - lib/functo/compose.rb
73
74
  - lib/functo/version.rb
74
75
  homepage: https://github.com/sbscully/functo
75
76
  licenses: