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 +4 -4
- data/.gitignore +1 -0
- data/README.md +55 -129
- data/lib/functo.rb +65 -79
- data/lib/functo/compose.rb +29 -0
- data/lib/functo/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1aacfe82c8c6c9ae6d012f3d8e6fdf0a76e95624
|
4
|
+
data.tar.gz: b214a8b047a2e3dfcfa825f407a59c85f8815706
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f913c2170fe113976ee62b1e3c4c3d89a6b9293db56c8612073b7e3f8d490bc859465ff11e8362e6380cd5ad14798ae06679211c6ffe65c5f418600cb85bd611
|
7
|
+
data.tar.gz: 6ef2cb2b623d76ce701fdbaeff494fb0966aced469551f4cd5bf0c4bd98c08fa4a12efb4c3c0085bdaefd5b6209f264a909206fdd7088dda76c16af92b1fa2bb
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,118 +1,53 @@
|
|
1
1
|
# Functo
|
2
2
|
|
3
|
-
|
3
|
+
Composable method objects in ruby.
|
4
4
|
|
5
|
-
|
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
|
8
|
+
class AddsOne
|
30
9
|
include Functo.call :add, :number
|
31
10
|
|
32
11
|
def add
|
33
|
-
number +
|
12
|
+
number + 1
|
34
13
|
end
|
35
14
|
end
|
36
|
-
```
|
37
|
-
|
38
|
-
## Installation
|
39
15
|
|
40
|
-
|
16
|
+
AddsOne[1]
|
17
|
+
# => 2
|
41
18
|
|
42
|
-
|
43
|
-
|
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
|
-
|
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
|
-
|
83
|
-
|
84
|
-
```ruby
|
85
|
-
AddsTwo[3]
|
86
|
-
# => 5
|
27
|
+
Multiplies[2, 3]
|
28
|
+
# => 6
|
87
29
|
```
|
88
30
|
|
89
|
-
|
31
|
+
Functo objects can be used in place of a `Proc`.
|
90
32
|
|
91
33
|
```ruby
|
92
|
-
[1, 2, 3].map(&
|
93
|
-
# => [3, 4
|
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
|
-
|
41
|
+
MultipliesAddsOne = Multiplies >> AddsOne
|
102
42
|
|
103
|
-
|
104
|
-
#
|
105
|
-
|
106
|
-
MultiAdd = MultipliesThree >> AddsTwo
|
107
|
-
|
108
|
-
MultiAdd[3]
|
109
|
-
# => 11
|
43
|
+
MultipliesAddsOne[2, 3]
|
44
|
+
# => 7
|
110
45
|
```
|
111
46
|
|
112
|
-
|
47
|
+
`>>` splats intermediate results. Use `>` to compose without splatting intermediate results.
|
113
48
|
|
114
49
|
```ruby
|
115
|
-
class
|
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
|
124
|
-
include Functo.call :sum, :
|
58
|
+
class Sums
|
59
|
+
include Functo.call :sum, :arr
|
125
60
|
|
126
61
|
def sum
|
127
|
-
|
62
|
+
arr.reduce(:+)
|
128
63
|
end
|
129
64
|
end
|
130
65
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
# => 6
|
66
|
+
SumsDigits = SplitsDigits >> Sums
|
67
|
+
SumsDigits[1066]
|
68
|
+
# => ArgumentError: wrong number of arguments (4 for 1)
|
135
69
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
# => ArgumentError: wrong number of arguments (given 1, expected 3)
|
70
|
+
SumsDigits2 = SplitsDigits > Sums
|
71
|
+
SumsDigits2[1066]
|
72
|
+
# => 13
|
140
73
|
```
|
141
74
|
|
142
|
-
|
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
|
-
|
148
|
-
include Functo.call :validate, :number
|
149
|
-
|
150
|
-
ValidationError = Class.new(StandardError)
|
78
|
+
SquareRoots = Functo.wrap ->(n) { Math.sqrt(n) }
|
151
79
|
|
152
|
-
|
153
|
-
|
80
|
+
SquareRootsAddsOne = SquareRoots >> AddsOne
|
81
|
+
SquareRootsAddsOne[16]
|
82
|
+
# => 5.0
|
83
|
+
```
|
154
84
|
|
155
|
-
|
156
|
-
end
|
157
|
-
end
|
85
|
+
### Filters
|
158
86
|
|
159
|
-
|
160
|
-
|
87
|
+
```ruby
|
88
|
+
class DividesTwo
|
89
|
+
include Functo.call :divide, number: ->(n) { Float(n) }
|
161
90
|
|
162
|
-
def
|
163
|
-
|
91
|
+
def divide
|
92
|
+
2 / number
|
164
93
|
end
|
165
94
|
end
|
166
95
|
|
167
|
-
|
168
|
-
# =>
|
169
|
-
|
170
|
-
AddsThree['10']
|
171
|
-
# => ValidationError
|
96
|
+
DividesTwo['4']
|
97
|
+
# => 0.5
|
172
98
|
```
|
173
99
|
|
174
|
-
|
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
|
184
|
-
include Functo.call :
|
111
|
+
class Squares
|
112
|
+
include Functo.call :square, number: Types::Strict::Int
|
185
113
|
|
186
|
-
def
|
187
|
-
number
|
114
|
+
def square
|
115
|
+
number**2
|
188
116
|
end
|
189
117
|
end
|
190
118
|
|
191
|
-
|
192
|
-
# =>
|
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
|
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
|
-
|
5
|
+
MAX_ATTRIBUTES = 3
|
5
6
|
PASS = '__FUNCTO_PASS__'.freeze
|
6
7
|
|
7
|
-
|
8
|
+
class << self
|
9
|
+
private :new
|
8
10
|
|
9
|
-
|
10
|
-
|
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
|
-
|
13
|
-
|
18
|
+
def wrap(obj)
|
19
|
+
define_method_object do |*args|
|
20
|
+
obj.call(*args)
|
21
|
+
end
|
14
22
|
end
|
15
23
|
|
16
|
-
|
17
|
-
|
18
|
-
|
24
|
+
def pass
|
25
|
+
PASS
|
26
|
+
end
|
19
27
|
|
20
|
-
|
21
|
-
|
22
|
-
|
28
|
+
def call(*args)
|
29
|
+
new(*parse_args(args))
|
30
|
+
end
|
23
31
|
|
24
|
-
|
25
|
-
output = names.shift
|
32
|
+
private
|
26
33
|
|
27
|
-
|
28
|
-
inputs =
|
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
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
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(
|
45
|
-
@
|
57
|
+
def initialize(attributes, function, filters)
|
58
|
+
@attributes = attributes
|
59
|
+
@function = function
|
46
60
|
@filters = filters
|
47
|
-
|
48
|
-
@
|
49
|
-
@
|
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(@
|
58
|
-
host.extend(@
|
71
|
+
host.include(@attributes_module)
|
72
|
+
host.extend(@function_module)
|
59
73
|
|
60
|
-
host.extend(
|
74
|
+
host.extend(Functo::Compose)
|
61
75
|
end
|
62
76
|
|
63
77
|
def define_initialize
|
64
|
-
ivars = @
|
65
|
-
|
66
|
-
|
78
|
+
ivars = @attributes.map { |name| "@#{name}" }
|
79
|
+
size = @attributes.size
|
80
|
+
filter = method(:apply_filters).to_proc
|
67
81
|
|
68
|
-
@
|
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
|
-
|
87
|
+
message = "wrong number of arguments (#{args_size} for #{size})"
|
88
|
+
|
89
|
+
raise ArgumentError.new(message)
|
74
90
|
end
|
75
91
|
|
76
|
-
|
77
|
-
|
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
|
-
|
102
|
+
attributes = @attributes
|
86
103
|
|
87
|
-
@
|
88
|
-
attr_reader(*
|
89
|
-
protected(*
|
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
|
-
|
111
|
+
function = @function
|
95
112
|
|
96
|
-
@
|
113
|
+
@function_module.class_eval do
|
97
114
|
define_method :call do |*args|
|
98
|
-
new(*args).public_send(
|
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
|
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
|
data/lib/functo/version.rb
CHANGED
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.
|
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:
|