invokable 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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