invokable 0.6.0 → 0.7.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +25 -8
- data/Gemfile.lock +1 -1
- data/README.md +65 -55
- data/lib/core_ext.rb +15 -0
- data/lib/invokable.rb +42 -17
- data/lib/invokable/array.rb +11 -8
- data/lib/invokable/closure.rb +2 -3
- data/lib/invokable/command.rb +2 -3
- data/lib/invokable/compose.rb +14 -8
- data/lib/invokable/core.rb +30 -14
- data/lib/invokable/data.rb +1 -0
- data/lib/invokable/hash.rb +11 -7
- data/lib/invokable/helpers.rb +141 -0
- data/lib/invokable/set.rb +11 -7
- data/lib/invokable/version.rb +1 -1
- metadata +5 -5
- data/lib/invokable/method.rb +0 -5
- data/lib/invokable/proc.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8285e8008ed711e2898713904adabd60c274f60a5d21b0995443c62bc9bd5ad9
|
4
|
+
data.tar.gz: 935b77cfdc76c309cd7bb6866fb85cfa823f8fa7cefc399fd7b6e6e1018b7f83
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8d501dca816dbdf3b49381f55b771564975886a1f3cd2d96946142301abaf833e5733ca77741e45ab3ec9975878b61022640d8c90226426a5e25c79d28734709
|
7
|
+
data.tar.gz: c4719b7d18dd692b933c2648504cd511136481c2256b0c670ecd09e1adbf7d7a9e31b17b2bb45b8ef7cf725a5063dd970c6344efef7c84c0fd3c4d269f30bfa5
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,19 +1,36 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
-
## 0.
|
3
|
+
## 0.7.0
|
4
4
|
|
5
|
-
- `
|
6
|
-
|
7
|
-
puma bootup in Rails 5.2.4.1.
|
5
|
+
- Added helper methods `juxtapose`, `knit`, `compose`, `identity`, `always`, `guarded`, `partial`, and `coerce`
|
6
|
+
that can be used as module functions on `Invokable` or in classes that mixin the `Invokable` module.
|
8
7
|
|
9
|
-
|
8
|
+
- For classes whose initialize method takes no arguments, when the class method `call` is called it will
|
9
|
+
initialize the class and call it's `call` method.
|
10
10
|
|
11
|
-
-
|
11
|
+
- `[]` and `===` are added to classed that mixin `Invokable` for better `Proc` compatibility.
|
12
12
|
|
13
|
-
|
13
|
+
- `Array`, `Hash` and `Set` patches no longer include the invokable methods, they simply add `to_proc`.
|
14
14
|
|
15
|
-
- `
|
15
|
+
- When `invokable/data` is required the array patch is also loaded.
|
16
|
+
|
17
|
+
- All the methods that take an invokable will "coerce" the invokable by simply returning it if it implements `call`
|
18
|
+
or coercing it into a proc if it implements `to_proc`.
|
16
19
|
|
17
20
|
## 0.6.0
|
18
21
|
|
19
22
|
- `Invokable::Closure` deprecated comparable behavior has been added to `Invokable` itself.
|
23
|
+
|
24
|
+
## 0.5.2
|
25
|
+
|
26
|
+
- `Invokable::Command` deprecated in favor of `Invokable::Closure`.
|
27
|
+
|
28
|
+
## 0.5.0
|
29
|
+
|
30
|
+
- Added `Invokable::Command` and `Invokable::Core#arity`
|
31
|
+
|
32
|
+
## 0.4.2
|
33
|
+
|
34
|
+
- `invokable/array` is no longer loaded with `invokable/data`.
|
35
|
+
This created a bit of havok in a few places. Including breaking
|
36
|
+
puma bootup in Rails 5.2.4.1.
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -3,10 +3,12 @@
|
|
3
3
|
|
4
4
|
# Invokable
|
5
5
|
|
6
|
-
Objects are functions! Treat any Object, Hashes, Arrays, and Sets as Procs (like Enumerable but for Procs)
|
6
|
+
Objects are functions! Treat any Object, Class, Hashes, Arrays, and Sets as Procs (like Enumerable but for Procs)
|
7
7
|
|
8
8
|
## Synopsis
|
9
9
|
|
10
|
+
Hashes can be treated as functions of their keys
|
11
|
+
|
10
12
|
```ruby
|
11
13
|
require 'invokable'
|
12
14
|
require 'invokable/hash'
|
@@ -15,6 +17,8 @@ number_names = { 1 => "One", 2 => "Two", 3 => "Three" }
|
|
15
17
|
[1, 2, 3, 4].map(&number_names) # => ["One", "Two", "Three", nil]
|
16
18
|
```
|
17
19
|
|
20
|
+
Arrays can be treated as functions of their indexes
|
21
|
+
|
18
22
|
```ruby
|
19
23
|
require 'invokable'
|
20
24
|
require 'invokable/array'
|
@@ -23,6 +27,8 @@ alpha = ('a'..'z').to_a
|
|
23
27
|
[1, 2, 3, 4].map(&alpha) # => ["b", "c", "d", "e"]
|
24
28
|
```
|
25
29
|
|
30
|
+
Sets can be treated as predicates
|
31
|
+
|
26
32
|
```ruby
|
27
33
|
require 'invokable'
|
28
34
|
require 'invokable/set'
|
@@ -31,23 +37,9 @@ favorite_numbers = Set[3, Math::PI]
|
|
31
37
|
[1, 2, 3, 4].select(&favorite_numbers) # => [3]
|
32
38
|
```
|
33
39
|
|
34
|
-
|
35
|
-
# service objects
|
36
|
-
require 'invokable'
|
37
|
-
|
38
|
-
class GetDataFromSomeService
|
39
|
-
include Invokable
|
40
|
-
|
41
|
-
def call(user)
|
42
|
-
# do the dirt
|
43
|
-
end
|
44
|
-
end
|
40
|
+
Objects that enclose state, can be treated as automatically curried functions.
|
45
41
|
|
46
|
-
data_for_user = GetDataFromSomeService.new.memoize # 'memoize' makes a proc that caches results
|
47
|
-
User.all.map(&data_for_user)
|
48
|
-
```
|
49
42
|
```ruby
|
50
|
-
# command objects that enclose state, can be treated as automatically curried functions.
|
51
43
|
require 'invokable'
|
52
44
|
|
53
45
|
class TwitterPoster
|
@@ -72,14 +64,69 @@ TwitterPoster.call(Model.find(1), current_user) # => #<TwitterStatus ...>
|
|
72
64
|
Model.where(created_at: Date.today).map(&TwitterPoster) # => [#<TwitterPoster ...>, ...]
|
73
65
|
```
|
74
66
|
|
75
|
-
Use
|
67
|
+
Use `memoize`, `<<` and `>>` for composition and other methods on Proc on your command objects
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
# app/queries/filter_records.rb
|
71
|
+
class FilterRecords
|
72
|
+
include Invokable
|
73
|
+
|
74
|
+
def initialize(params)
|
75
|
+
@params = params
|
76
|
+
end
|
77
|
+
|
78
|
+
def call(params)
|
79
|
+
# do filtering return records
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# app/queries/sort_records.rb
|
84
|
+
class SortRecords
|
85
|
+
include Invokable
|
86
|
+
|
87
|
+
def initialize(params)
|
88
|
+
@params = params
|
89
|
+
end
|
90
|
+
|
91
|
+
def call(records)
|
92
|
+
# do sorting return records
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
sort_and_filter = SortRecords.call(params) << FilterRecords.call(params)
|
97
|
+
sort_and_filter.call(records) # => sorted and filtered records
|
98
|
+
```
|
99
|
+
|
100
|
+
Helper methods that can be used with any object that responds to `call` or `to_proc`
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
Invokable.juxtapose(:sum, -> (xs) { xs.reduce(:*) }, :min, :max).([3, 4, 6]) # => [13, 72, 3, 6]
|
104
|
+
|
105
|
+
Invokable.knit(:upcase, :downcase).(['FoO', 'BaR']) # => ["FOO", "bar"]
|
106
|
+
```
|
107
|
+
|
108
|
+
They are also mixed into any class that includes the module
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
class Transformer
|
112
|
+
include Invokable
|
113
|
+
|
114
|
+
def call(array)
|
115
|
+
array.map(&juxtapose(identity, compose(:to_s, :upcase))).to_h
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
Transformer.call([:a, :b, :c, :d]) # => {:a => "A", :b => "B", :c => "C", :d => "D"}
|
120
|
+
```
|
121
|
+
|
122
|
+
Use as much or a little as you need
|
76
123
|
|
77
124
|
```ruby
|
78
125
|
require 'invokable' # loads Invokable module
|
79
126
|
require 'invokable/hash' # loads hash patch
|
80
127
|
require 'invokable/array' # loads array patch
|
81
128
|
require 'invokable/set' # loads set patch
|
82
|
-
require 'invokable/data' # loads hash and
|
129
|
+
require 'invokable/data' # loads hash, set and array patches
|
83
130
|
```
|
84
131
|
|
85
132
|
## Why?
|
@@ -104,49 +151,12 @@ Or install it yourself as:
|
|
104
151
|
|
105
152
|
> gem install invokable
|
106
153
|
|
107
|
-
## API
|
108
|
-
|
109
|
-
### `to_proc => Proc`
|
110
|
-
|
111
|
-
```ruby
|
112
|
-
hash = { a: 1, b, 2 }
|
113
|
-
hash.class # => Hash
|
114
|
-
hash.to_proc.class # => Proc
|
115
|
-
[:a, :b].map(&hash) # => [1, 2]
|
116
|
-
```
|
117
|
-
|
118
|
-
Convert an object into a proc. When the `Invokable` module is included in a class it will do this by
|
119
|
-
returning a proc that passes it's arguments to the object's `call` method. When `invokable/data` is
|
120
|
-
loaded `Hash#call` is mapped to `Hash#dig`, `Array#call` is mapped to `Array#at`, and `Set#call`
|
121
|
-
is mapped to `Set#include?`.
|
122
|
-
|
123
|
-
### `curry(arity = nil) => Proc`
|
124
|
-
|
125
|
-
Returns a curried proc. If the `arity` is given, it determines the number of arguments.
|
126
|
-
(see [Proc#curry](https://ruby-doc.org/core-2.7.0/Proc.html#method-i-curry)).
|
127
|
-
|
128
|
-
### `memoize => Proc`
|
129
|
-
|
130
|
-
Returns a memoized proc, that is, a proc that caches it's return values by it's arguments.
|
131
|
-
|
132
|
-
### `<<(invokable) => Proc`
|
133
|
-
|
134
|
-
Returns a proc that is a composition of this invokable and the given invokable.
|
135
|
-
|
136
|
-
### `>>(invokable) => Proc`
|
137
|
-
|
138
|
-
Returns a proc that is a composition of this invokable and the given invokable.
|
139
|
-
|
140
154
|
## See Also
|
141
155
|
|
142
156
|
- [Closures and Objects are Equivalent](http://wiki.c2.com/?ClosuresAndObjectsAreEquivalent)
|
143
157
|
- [Clojure](https://clojure.org)
|
144
158
|
- [Arc](http://www.arclanguage.org)
|
145
159
|
|
146
|
-
## TODO
|
147
|
-
|
148
|
-
- benchmark Invokable#to_proc
|
149
|
-
|
150
160
|
## License
|
151
161
|
|
152
162
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/lib/core_ext.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
if RUBY_VERSION.split('.').take(2).join('.').to_f < 2.6
|
2
|
+
# Add {<<} and {>>} for right and left function composition.
|
3
|
+
#
|
4
|
+
# @note These methods were added to Ruby in version 2.6 so this patch will only be applied when running older versions.
|
5
|
+
class Proc
|
6
|
+
include Invokable::Compose
|
7
|
+
end
|
8
|
+
|
9
|
+
# Add {<<} and {>>} for right and left function composition.
|
10
|
+
#
|
11
|
+
# @note These methods were added to Ruby in version 2.6 so this patch will only be applied when running older versions.
|
12
|
+
class Method
|
13
|
+
include Invokable::Compose
|
14
|
+
end
|
15
|
+
end
|
data/lib/invokable.rb
CHANGED
@@ -1,28 +1,52 @@
|
|
1
1
|
require 'invokable/version'
|
2
2
|
require 'invokable/core'
|
3
|
-
require 'invokable/
|
4
|
-
|
5
|
-
# TODO: make use of Gem::Version
|
6
|
-
if RUBY_VERSION.split('.').take(2).join('.').to_f < 2.6
|
7
|
-
require 'invokable/proc'
|
8
|
-
require 'invokable/method'
|
9
|
-
end
|
3
|
+
require 'invokable/helpers'
|
4
|
+
require 'core_ext'
|
10
5
|
|
6
|
+
# A module that attempts to generalize the notion of a first class function or Procs as they are often called
|
7
|
+
# in Ruby. It enables any class and it's objects to be treated as first-class functions. It also provides helpers
|
8
|
+
# includes helpers that can be used for performing higher-order operations on and object that can be treated
|
9
|
+
# as a function.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# class TwitterPoster
|
13
|
+
# include Invokable
|
14
|
+
#
|
15
|
+
# def initialize(model)
|
16
|
+
# @model = model
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# def call(user)
|
20
|
+
# # do the dirt
|
21
|
+
# ...
|
22
|
+
# TwitterStatus.new(user, data)
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# TwitterPoster.call(Model.find(1)) # => #<TwitterPoster ...>
|
27
|
+
# TwitterPoster.call(Model.find(1), current_user) # => #<TwitterStatus ...>
|
11
28
|
module Invokable
|
29
|
+
extend Invokable::Helpers
|
30
|
+
|
12
31
|
def self.included(base)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
32
|
+
INCLUDED_MODULES.each do |mod|
|
33
|
+
base.include(mod)
|
34
|
+
base.extend(mod)
|
35
|
+
end
|
17
36
|
base.extend(ClassMethods)
|
18
37
|
end
|
19
38
|
|
39
|
+
private
|
40
|
+
|
41
|
+
INCLUDED_MODULES = [Core, Helpers].freeze
|
42
|
+
|
43
|
+
# The methods that are mixed into any class at the class level that includes {Invokable}.
|
44
|
+
#
|
45
|
+
# @note The module should not be used directly.
|
20
46
|
module ClassMethods
|
21
47
|
# Return the "total" arity of the class (i.e. the arity of the initializer and the arity of the call method)
|
22
48
|
#
|
23
49
|
# @version 0.6.0
|
24
|
-
# @see https://ruby-doc.org/core-2.7.1/Proc.html#method-i-arity Proc#arity
|
25
|
-
# @see initializer_arity
|
26
50
|
#
|
27
51
|
# @return [Integer]
|
28
52
|
def arity
|
@@ -34,12 +58,13 @@ module Invokable
|
|
34
58
|
# and return the results of the `call` method.
|
35
59
|
#
|
36
60
|
# @version 0.6.0
|
61
|
+
#
|
37
62
|
# @see arity
|
38
|
-
# @see initializer_arity
|
39
63
|
def call(*args)
|
40
|
-
|
41
|
-
|
42
|
-
|
64
|
+
return new.call if arity == 0
|
65
|
+
return new(*args) if args.length == initializer_arity
|
66
|
+
|
67
|
+
if args.length == arity
|
43
68
|
init_args = args.slice(0, initializer_arity)
|
44
69
|
call_args = args.slice(initializer_arity, args.length)
|
45
70
|
new(*init_args).call(*call_args)
|
data/lib/invokable/array.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
# Extend core Array object by aliasing it's `[]` method as `call`,
|
4
|
-
# and including the `Invokable` module.
|
5
|
-
#
|
6
|
-
# @see https://ruby-doc.org/core-2.7.0/Array.html#method-i-5B-5D Array#[]
|
1
|
+
# Extend core Array by adding {to_proc} which will enable arrays to be treated as functions
|
2
|
+
# of their indexes.
|
7
3
|
class Array
|
8
|
-
|
9
|
-
|
4
|
+
# Return a proc that takes the index of the array and returns the value at that index or nil
|
5
|
+
# if there is no value at the given index.
|
6
|
+
#
|
7
|
+
# @return [Proc]
|
8
|
+
def to_proc
|
9
|
+
lambda do |index|
|
10
|
+
at(index)
|
11
|
+
end
|
12
|
+
end
|
10
13
|
end
|
data/lib/invokable/closure.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
module Invokable
|
2
2
|
# Treat classes as curried functions
|
3
3
|
#
|
4
|
-
# @see https://ruby-doc.org/core-2.7.0/Proc.html#method-i-curry Proc#curry
|
5
|
-
#
|
6
4
|
# @version 0.5.2
|
7
|
-
#
|
5
|
+
#
|
6
|
+
# @deprecated These features are included in the module {Invokable} by default now.
|
8
7
|
module Closure
|
9
8
|
def self.included(klass)
|
10
9
|
klass.include(Invokable)
|
data/lib/invokable/command.rb
CHANGED
@@ -3,10 +3,9 @@ require_relative 'closure'
|
|
3
3
|
module Invokable
|
4
4
|
# Treat "Command Objects" as curried functions
|
5
5
|
#
|
6
|
-
# @see https://ruby-doc.org/core-2.7.0/Proc.html#method-i-curry Proc#curry
|
7
|
-
#
|
8
6
|
# @version 0.5.0
|
9
|
-
#
|
7
|
+
#
|
8
|
+
# @deprecated These features are included in the {Invokable} module by default now.
|
10
9
|
module Command
|
11
10
|
def self.included(klass)
|
12
11
|
klass.include(Invokable)
|
data/lib/invokable/compose.rb
CHANGED
@@ -1,24 +1,30 @@
|
|
1
1
|
module Invokable
|
2
|
+
# The {<<} and {>>} methods for right and left function composition.
|
3
|
+
#
|
4
|
+
# This module is included in the {Invokable} module and the Proc and Method classes
|
5
|
+
# when used with versions of Ruby earlier than 2.6 (when they were added).
|
6
|
+
#
|
7
|
+
# @note This module should not be used directly.
|
2
8
|
module Compose
|
3
|
-
# Return a proc that is the composition of this invokable and the given
|
4
|
-
# The returned proc takes a variable number of arguments, calls
|
9
|
+
# Return a proc that is the composition of this invokable and the given invokable.
|
10
|
+
# The returned proc takes a variable number of arguments, calls invokable with
|
5
11
|
# them then calls this proc with the result.
|
6
12
|
#
|
7
13
|
# @return [Proc]
|
8
14
|
def <<(invokable)
|
9
|
-
|
10
|
-
call(invokable.call(*args))
|
15
|
+
lambda do |*args|
|
16
|
+
call(Invokable.coerce(invokable).call(*args))
|
11
17
|
end
|
12
18
|
end
|
13
19
|
|
14
|
-
# Return a proc that is the composition of this invokable and the given
|
15
|
-
# The returned proc takes a variable number of arguments, calls
|
20
|
+
# Return a proc that is the composition of this invokable and the given invokable.
|
21
|
+
# The returned proc takes a variable number of arguments, calls invokable with
|
16
22
|
# them then calls this proc with the result.
|
17
23
|
#
|
18
24
|
# @return [Proc]
|
19
25
|
def >>(invokable)
|
20
|
-
|
21
|
-
invokable.call(call(*args))
|
26
|
+
lambda do |*args|
|
27
|
+
Invokable.coerce(invokable).call(call(*args))
|
22
28
|
end
|
23
29
|
end
|
24
30
|
end
|
data/lib/invokable/core.rb
CHANGED
@@ -1,26 +1,27 @@
|
|
1
|
+
require_relative 'compose'
|
2
|
+
|
1
3
|
module Invokable
|
4
|
+
# The core methods that are mixed into classes at a class and instance level when they
|
5
|
+
# include {Invokable}.
|
6
|
+
#
|
7
|
+
# @note This module should not be used directly.
|
2
8
|
module Core
|
3
|
-
|
9
|
+
include Compose
|
10
|
+
|
11
|
+
# Return a Proc that forwards it's arguments along to call.
|
4
12
|
#
|
5
|
-
# @see https://ruby-doc.org/core-2.7.0/Proc.html#method-i-call Proc#call
|
6
13
|
# @return [Proc]
|
7
14
|
def to_proc
|
8
|
-
|
9
|
-
|
10
|
-
Proc.new do |*args|
|
11
|
-
call(*args)
|
12
|
-
end
|
13
|
-
else
|
14
|
-
raise "Don't know how to convert #{self.inspect} into a Proc"
|
15
|
+
lambda do |*args|
|
16
|
+
call(*args)
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
18
|
-
# Return a curried proc. If the optional
|
20
|
+
# Return a curried proc. If the optional arity argument is given, it determines the number of arguments.
|
19
21
|
# A curried proc receives some arguments. If a sufficient number of arguments are supplied, it passes the
|
20
22
|
# supplied arguments to the original proc and returns the result. Otherwise, returns another curried proc
|
21
23
|
# that takes the rest of arguments.
|
22
24
|
#
|
23
|
-
# @see https://ruby-doc.org/core-2.7.0/Proc.html#method-i-curry Proc#curry
|
24
25
|
# @param arity [Integer]
|
25
26
|
# @return [Proc]
|
26
27
|
def curry(arity = nil)
|
@@ -31,19 +32,34 @@ module Invokable
|
|
31
32
|
#
|
32
33
|
# @return [Proc]
|
33
34
|
def memoize
|
34
|
-
|
35
|
+
lambda do |*args|
|
35
36
|
@memo ||= {}
|
36
37
|
@memo[args.hash] ||= call(*args)
|
37
38
|
end
|
38
39
|
end
|
39
40
|
|
40
|
-
# Return the arity (i.e. the number of arguments) of the
|
41
|
+
# Return the arity (i.e. the number of arguments) of the "call" method.
|
41
42
|
#
|
42
43
|
# @version 0.5.0
|
43
|
-
#
|
44
|
+
#
|
44
45
|
# @return [Integer]
|
45
46
|
def arity
|
46
47
|
method(:call).arity
|
47
48
|
end
|
49
|
+
|
50
|
+
# For Proc compatibility, forwards it's arguments to "call".
|
51
|
+
#
|
52
|
+
# @version 0.7.0
|
53
|
+
def [](*args)
|
54
|
+
call(*args)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Call invokable with one argument, allows invocables to be used in case statements
|
58
|
+
# and Enumerable#grep.
|
59
|
+
#
|
60
|
+
# @version 0.7.0
|
61
|
+
def ===(obj)
|
62
|
+
call(obj)
|
63
|
+
end
|
48
64
|
end
|
49
65
|
end
|
data/lib/invokable/data.rb
CHANGED
data/lib/invokable/hash.rb
CHANGED
@@ -1,12 +1,16 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
# Extend core Hash object by aliasing it's `dig` method as `call`,
|
4
|
-
# and including the `Invokable` module.
|
1
|
+
# Extend core Hash object by adding {to_proc} so hashes can be treated as functions of their keys.
|
5
2
|
#
|
6
|
-
# @
|
3
|
+
# @note {to_proc} was added to Hash in Ruby 2.7 so this patch is only applied for older versions of Ruby.
|
7
4
|
class Hash
|
8
5
|
if RUBY_VERSION.split('.').take(2).join('.').to_f < 2.7
|
9
|
-
|
10
|
-
|
6
|
+
# Return a proc that takes a key and returns the value associated with it or nil if the key
|
7
|
+
# is not present in the hash.
|
8
|
+
#
|
9
|
+
# @return [Proc]
|
10
|
+
def to_proc
|
11
|
+
lambda do |key|
|
12
|
+
fetch(key) { nil }
|
13
|
+
end
|
14
|
+
end
|
11
15
|
end
|
12
16
|
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
module Invokable
|
2
|
+
# A collection of helper methods for working with invokables where they accept and invokable
|
3
|
+
# as an argument they will "coerce" the value (see {coerce}). This enables any method that
|
4
|
+
# implements `call` or `to_proc` to be treated as an invokable.
|
5
|
+
#
|
6
|
+
# @version 0.7.0
|
7
|
+
module Helpers
|
8
|
+
# Return a proc that returns the value that is passed to it.
|
9
|
+
#
|
10
|
+
# @version 0.7.0
|
11
|
+
#
|
12
|
+
# @return [Proc]
|
13
|
+
def identity
|
14
|
+
lambda do |x|
|
15
|
+
x
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Return a proc that will always return the value given it.
|
20
|
+
#
|
21
|
+
# @version 0.7.0
|
22
|
+
#
|
23
|
+
# @return [Proc]
|
24
|
+
def always(x)
|
25
|
+
lambda do
|
26
|
+
x
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# If the invokable passed responds to :call it will be returned. If it responds to to_proc to_proc
|
31
|
+
# is called and the resulting proc is returned. Otherwise a TypeError will be raised.
|
32
|
+
#
|
33
|
+
# @version 0.7.0
|
34
|
+
def coerce(invokable)
|
35
|
+
return invokable if invokable.respond_to?(:call)
|
36
|
+
return invokable.to_proc if invokable.respond_to?(:to_proc)
|
37
|
+
|
38
|
+
raise TypeError, "#{invokable.inspect} is not a valid invokable"
|
39
|
+
end
|
40
|
+
|
41
|
+
# Return a proc that passes it's arguments to the given invokables and returns an array of results.
|
42
|
+
# The invokables passed will be coerced before they are called (see {coerce}).
|
43
|
+
#
|
44
|
+
# @version 0.7.0
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# juxtapose(:first, :count).call('A'..'Z') # => ["A", 26]
|
48
|
+
#
|
49
|
+
# @return [Proc]
|
50
|
+
def juxtapose(*invokables)
|
51
|
+
lambda do |*args|
|
52
|
+
invokables.map do |invokable|
|
53
|
+
coerce(invokable).call(*args)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
alias juxt juxtapose
|
58
|
+
|
59
|
+
# A relative of {juxtapose}--return a proc that takes a collection and calls the invokables
|
60
|
+
# on their corresponding values (in sequence) in the collection. The invokables passed
|
61
|
+
# will be coerced before they are called (see {coerce}).
|
62
|
+
#
|
63
|
+
# @version 0.7.0
|
64
|
+
#
|
65
|
+
# @example
|
66
|
+
# knit(:upcase, :downcase).call(['FoO', 'BaR']) # => ["FOO", "bar"]
|
67
|
+
#
|
68
|
+
# @return [Proc]
|
69
|
+
def knit(*invokables)
|
70
|
+
lambda do |enumerable|
|
71
|
+
results = []
|
72
|
+
enumerable.each_with_index do |x, i|
|
73
|
+
return results if invokables[i].nil?
|
74
|
+
|
75
|
+
results << coerce(invokables[i]).call(x)
|
76
|
+
end
|
77
|
+
results
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Return a proc that is a composition of the given invokables from right-to-left. The invokables
|
82
|
+
# passed will be coerced before they are called (see {coerce}).
|
83
|
+
#
|
84
|
+
# @version 0.7.0
|
85
|
+
#
|
86
|
+
# @example
|
87
|
+
# compose(:to_s, :upcase).call(:this_is_a_test) # => "THIS_IS_A_TEST"
|
88
|
+
#
|
89
|
+
# @return [Proc]
|
90
|
+
def compose(*invokables)
|
91
|
+
return identity if invokables.empty?
|
92
|
+
return coerce(invokables[0]) if invokables.count == 1
|
93
|
+
|
94
|
+
if invokables.count == 2
|
95
|
+
f, g = invokables
|
96
|
+
return lambda do |*args|
|
97
|
+
coerce(f).call(coerce(g).call(*args))
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
invokables.reduce do |a, b|
|
102
|
+
compose(a, b)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Return a proc that guards it's arguments from nil by replacing nil values with the alternative
|
107
|
+
# value that corresponds to it's place in the argument list. The invokable passed will be coerced
|
108
|
+
# before they are called (see {coerce}).
|
109
|
+
#
|
110
|
+
# @version 0.7.0
|
111
|
+
#
|
112
|
+
# @example
|
113
|
+
# count = guarded(:count, [])
|
114
|
+
# count.call(nil) # => 0
|
115
|
+
# count.call([1]) # => 1
|
116
|
+
#
|
117
|
+
# @return [Proc]
|
118
|
+
def guarded(invokable, *alternatives)
|
119
|
+
lambda do |*args|
|
120
|
+
new_args = args.each_with_index.map do |x, i|
|
121
|
+
x.nil? ? alternatives[i] : x
|
122
|
+
end
|
123
|
+
coerce(invokable).call(*new_args)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
alias fnil guarded
|
127
|
+
alias nil_safe guarded
|
128
|
+
|
129
|
+
# Given an invokable and and a fewer number of arguments that the invokable takes return
|
130
|
+
# a proc that will accept the rest of the arguments (i.e. a partialy applied function).
|
131
|
+
#
|
132
|
+
# @version 0.7.0
|
133
|
+
#
|
134
|
+
# @return [Proc]
|
135
|
+
def partial(invokable, *args)
|
136
|
+
lambda do |*other_args|
|
137
|
+
coerce(invokable).call(*(args + other_args))
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
data/lib/invokable/set.rb
CHANGED
@@ -1,11 +1,15 @@
|
|
1
1
|
require 'set'
|
2
|
-
require_relative 'core'
|
3
2
|
|
4
|
-
# Extend stdlib Set object by
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# @see https://ruby-doc.org/stdlib-2.7.0/libdoc/set/rdoc/Set.html#method-i-include-3F Set#include?
|
3
|
+
# Extend stdlib Set object by adding {to_proc} which will allow a set to be treated as a function of
|
4
|
+
# the membership of it's elements.
|
8
5
|
class Set
|
9
|
-
|
10
|
-
|
6
|
+
# Return a proc that takes an value and return true if the value is an element of the set, but returns
|
7
|
+
# false otherwise.
|
8
|
+
#
|
9
|
+
# @return [Proc]
|
10
|
+
def to_proc
|
11
|
+
lambda do |element|
|
12
|
+
include?(element)
|
13
|
+
end
|
14
|
+
end
|
11
15
|
end
|
data/lib/invokable/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: invokable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Delon Newman
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-08-
|
11
|
+
date: 2020-08-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -70,6 +70,7 @@ files:
|
|
70
70
|
- README.md
|
71
71
|
- Rakefile
|
72
72
|
- invokable.gemspec
|
73
|
+
- lib/core_ext.rb
|
73
74
|
- lib/invokable.rb
|
74
75
|
- lib/invokable/array.rb
|
75
76
|
- lib/invokable/closure.rb
|
@@ -78,8 +79,7 @@ files:
|
|
78
79
|
- lib/invokable/core.rb
|
79
80
|
- lib/invokable/data.rb
|
80
81
|
- lib/invokable/hash.rb
|
81
|
-
- lib/invokable/
|
82
|
-
- lib/invokable/proc.rb
|
82
|
+
- lib/invokable/helpers.rb
|
83
83
|
- lib/invokable/set.rb
|
84
84
|
- lib/invokable/version.rb
|
85
85
|
homepage: https://github.com/delonnewman/invokable
|
@@ -106,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
106
|
- !ruby/object:Gem::Version
|
107
107
|
version: '0'
|
108
108
|
requirements: []
|
109
|
-
rubygems_version: 3.0.
|
109
|
+
rubygems_version: 3.0.8
|
110
110
|
signing_key:
|
111
111
|
specification_version: 4
|
112
112
|
summary: Objects are functions! Treat any Object, Hashes, Arrays and Sets as Procs
|
data/lib/invokable/method.rb
DELETED