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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b1c89f44b80135945cce3300f6f9b8e5b1210282d8c3d10256c2593ccf281948
4
- data.tar.gz: 2a0e9ce82c10b9f57b1767087d56b5d53a67bdfeb139e5a642fb071a9bc6a8e6
3
+ metadata.gz: 8285e8008ed711e2898713904adabd60c274f60a5d21b0995443c62bc9bd5ad9
4
+ data.tar.gz: 935b77cfdc76c309cd7bb6866fb85cfa823f8fa7cefc399fd7b6e6e1018b7f83
5
5
  SHA512:
6
- metadata.gz: 20d8a431d84d973709f122211c8e606d4ac43e162c54a7bdf8e7507873c0235dede63a8e73a69d296152b6020ebd93a3001be8c73916dee7a7cfbcc6c600b0df
7
- data.tar.gz: c1df682d8c7eed2525d7a2eaadce3e5dedb8114b18d64df3f7e323995b7c30c31827d2c6f2da94738094c06614a74bf4309dc20a1d3a2e9b66152c6e265b7035
6
+ metadata.gz: 8d501dca816dbdf3b49381f55b771564975886a1f3cd2d96946142301abaf833e5733ca77741e45ab3ec9975878b61022640d8c90226426a5e25c79d28734709
7
+ data.tar.gz: c4719b7d18dd692b933c2648504cd511136481c2256b0c670ecd09e1adbf7d7a9e31b17b2bb45b8ef7cf725a5063dd970c6344efef7c84c0fd3c4d269f30bfa5
data/.gitignore CHANGED
@@ -10,3 +10,4 @@
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
12
  tags
13
+ .DS_Store
@@ -1,19 +1,36 @@
1
1
  # Change Log
2
2
 
3
- ## 0.4.2
3
+ ## 0.7.0
4
4
 
5
- - `invokable/array` is no longer loaded with `invokable/data`.
6
- This created a bit of havok in a few places. Including breaking
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
- ## 0.5.0
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
- - Added `Invokable::Command` and `Invokable::Core#arity`
11
+ - `[]` and `===` are added to classed that mixin `Invokable` for better `Proc` compatibility.
12
12
 
13
- ## 0.5.2
13
+ - `Array`, `Hash` and `Set` patches no longer include the invokable methods, they simply add `to_proc`.
14
14
 
15
- - `Invokable::Command` deprecated in favor of `Invokable::Closure`.
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.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- invokable (0.6.0)
4
+ invokable (0.7.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
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
- ```ruby
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 as much or a little as you need:
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 set patches
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).
@@ -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
@@ -1,28 +1,52 @@
1
1
  require 'invokable/version'
2
2
  require 'invokable/core'
3
- require 'invokable/compose'
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
- base.include(Invokable::Core)
14
- base.include(Invokable::Compose)
15
- base.extend(Invokable::Core)
16
- base.extend(Invokable::Compose)
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
- if args.length == initializer_arity
41
- new(*args)
42
- elsif args.length == arity
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)
@@ -1,10 +1,13 @@
1
- require_relative 'core'
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
- include Invokable::Core
9
- alias call []
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
@@ -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
- # @deprecated These features are included in the Invokable by default now.
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)
@@ -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
- # @deprecated These features are included in the Invokable by default now.
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)
@@ -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 `invokable`.
4
- # The returned proc takes a variable number of arguments, calls `invokable` with
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
- Proc.new do |*args|
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 `invokable`.
15
- # The returned proc takes a variable number of arguments, calls `invokable` with
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
- Proc.new do |*args|
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
@@ -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
- # If object responds to `call` convert into a Proc forwards it's arguments along to `call`.
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
- if respond_to?(:call)
9
- # TODO: Would method(:call) be more performant? We need benchmarks.
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 `arity` argument is given, it determines the number of arguments.
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
- Proc.new do |*args|
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 `call` method.
41
+ # Return the arity (i.e. the number of arguments) of the "call" method.
41
42
  #
42
43
  # @version 0.5.0
43
- # @see https://ruby-doc.org/core-2.7.1/Proc.html#method-i-arity Proc#arity
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
@@ -1,2 +1,3 @@
1
1
  require_relative 'hash'
2
2
  require_relative 'set'
3
+ require_relative 'array'
@@ -1,12 +1,16 @@
1
- require_relative 'core'
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
- # @see https://ruby-doc.org/core-2.7.0/Hash.html#method-i-dig Hash#dig
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
- include Invokable::Core
10
- alias call dig
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
@@ -1,11 +1,15 @@
1
1
  require 'set'
2
- require_relative 'core'
3
2
 
4
- # Extend stdlib Set object by aliasing it's `include?` method as `call`,
5
- # and including the `Invokable` module.
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
- include Invokable::Core
10
- alias call include?
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
@@ -1,3 +1,3 @@
1
1
  module Invokable
2
- VERSION = "0.6.0"
2
+ VERSION = "0.7.0"
3
3
  end
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.6.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-06 00:00:00.000000000 Z
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/method.rb
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.6
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
@@ -1,5 +0,0 @@
1
- require_relative 'compose'
2
-
3
- class Method
4
- include Invokable::Compose
5
- end
@@ -1,5 +0,0 @@
1
- require_relative 'compose'
2
-
3
- class Proc
4
- include Invokable::Compose
5
- end