invokable 0.4.2 → 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: 0f703edf97638d502bd35c3717fb629355e17f7f1d2468376698baec50c8b495
4
- data.tar.gz: a04e0e82205103781a0fff545a3a8dceb37a9eebf0103c01996331b4a060d877
3
+ metadata.gz: 8285e8008ed711e2898713904adabd60c274f60a5d21b0995443c62bc9bd5ad9
4
+ data.tar.gz: 935b77cfdc76c309cd7bb6866fb85cfa823f8fa7cefc399fd7b6e6e1018b7f83
5
5
  SHA512:
6
- metadata.gz: e79339f1c7b207ac86f2c083009e6a51972099e57fa03e3c45f45bb1b0dddf2f9c1f3fd33adab6230dac951466f3fba9b9671130c40af283ff9d2439f98fe919
7
- data.tar.gz: 87263922f9790fc9dda1acc43df77cd0c3077f57998d2364ad91bdd27b2d0495455369a3a18c8026eac2623d4d2f32e1e05b61a89f9e2d55e15197329848a7cd
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,5 +1,34 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.7.0
4
+
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.
7
+
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
+
11
+ - `[]` and `===` are added to classed that mixin `Invokable` for better `Proc` compatibility.
12
+
13
+ - `Array`, `Hash` and `Set` patches no longer include the invokable methods, they simply add `to_proc`.
14
+
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`.
19
+
20
+ ## 0.6.0
21
+
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
+
3
32
  ## 0.4.2
4
33
 
5
34
  - `invokable/array` is no longer loaded with `invokable/data`.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- invokable (0.4.2)
4
+ invokable (0.7.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -3,55 +3,130 @@
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
- require 'invokable/hash'
13
+ require 'invokable'
14
+ require 'invokable/hash'
12
15
 
13
- number_names = { 1 => "One", 2 => "Two", 3 => "Three" }
14
- [1, 2, 3, 4].map(&number_names) # => ["One", "Two", "Three", nil]
16
+ number_names = { 1 => "One", 2 => "Two", 3 => "Three" }
17
+ [1, 2, 3, 4].map(&number_names) # => ["One", "Two", "Three", nil]
15
18
  ```
16
19
 
20
+ Arrays can be treated as functions of their indexes
21
+
17
22
  ```ruby
18
- require 'invokable/array'
23
+ require 'invokable'
24
+ require 'invokable/array'
19
25
 
20
- alpha = ('a'..'z').to_a
21
- [1, 2, 3, 4].map(&alpha) # => ["b", "c", "d", "e"]
26
+ alpha = ('a'..'z').to_a
27
+ [1, 2, 3, 4].map(&alpha) # => ["b", "c", "d", "e"]
22
28
  ```
23
29
 
30
+ Sets can be treated as predicates
31
+
24
32
  ```ruby
25
- require 'invokable/set'
33
+ require 'invokable'
34
+ require 'invokable/set'
35
+
36
+ favorite_numbers = Set[3, Math::PI]
37
+ [1, 2, 3, 4].select(&favorite_numbers) # => [3]
38
+ ```
39
+
40
+ Objects that enclose state, can be treated as automatically curried functions.
41
+
42
+ ```ruby
43
+ require 'invokable'
44
+
45
+ class TwitterPoster
46
+ include Invokable
47
+
48
+ def initialize(model)
49
+ @model = model
50
+ end
51
+
52
+ def call(user)
53
+ # do the dirt
54
+ ...
55
+ TwitterStatus.new(user, data)
56
+ end
57
+ end
58
+
59
+ TwitterPoster.call(Model.find(1)) # => #<TwitterPoster ...>
60
+ TwitterPoster.call(Model.find(1), current_user) # => #<TwitterStatus ...>
61
+
62
+ # both the class and it's instances can be used anywhere Procs are.
63
+
64
+ Model.where(created_at: Date.today).map(&TwitterPoster) # => [#<TwitterPoster ...>, ...]
65
+ ```
66
+
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
26
86
 
27
- favorite_numbers = Set[3, Math::PI]
28
- [1, 2, 3, 4].select(&favorite_numbers) # => [3]
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
29
98
  ```
30
99
 
100
+ Helper methods that can be used with any object that responds to `call` or `to_proc`
101
+
31
102
  ```ruby
32
- require 'invokable'
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
+ ```
33
107
 
34
- # service objects
35
- class GetDataFromSomeService
36
- include Invokable
108
+ They are also mixed into any class that includes the module
37
109
 
38
- def call(user)
39
- # do the dirt
40
- end
110
+ ```ruby
111
+ class Transformer
112
+ include Invokable
113
+
114
+ def call(array)
115
+ array.map(&juxtapose(identity, compose(:to_s, :upcase))).to_h
41
116
  end
117
+ end
42
118
 
43
- data_for_user = GetDataFromSomeService.new.memoize # 'memoize' makes a proc that caches results
44
- User.all.map(&data_for_user)
119
+ Transformer.call([:a, :b, :c, :d]) # => {:a => "A", :b => "B", :c => "C", :d => "D"}
45
120
  ```
46
121
 
47
- Use as much or a little as you need:
122
+ Use as much or a little as you need
48
123
 
49
124
  ```ruby
50
125
  require 'invokable' # loads Invokable module
51
126
  require 'invokable/hash' # loads hash patch
52
127
  require 'invokable/array' # loads array patch
53
128
  require 'invokable/set' # loads set patch
54
- require 'invokable/data' # loads hash and set patches
129
+ require 'invokable/data' # loads hash, set and array patches
55
130
  ```
56
131
 
57
132
  ## Why?
@@ -76,48 +151,12 @@ Or install it yourself as:
76
151
 
77
152
  > gem install invokable
78
153
 
79
- ## API
80
-
81
- ### `to_proc => Proc`
82
-
83
- ```ruby
84
- hash = { a: 1, b, 2 }
85
- hash.class # => Hash
86
- hash.to_proc.class # => Proc
87
- [:a, :b].map(&hash) # => [1, 2]
88
- ```
89
-
90
- Convert an object into a proc. When the `Invokable` module is included in a class it will do this by
91
- returning a proc that passes it's arguments to the object's `call` method. When `invokable/data` is
92
- loaded `Hash#call` is mapped to `Hash#dig`, `Array#call` is mapped to `Array#at`, and `Set#call`
93
- is mapped to `Set#include?`.
94
-
95
- ### `curry(arity = nil) => Proc`
96
-
97
- Returns a curried proc. If the `arity` is given, it determines the number of arguments.
98
- (see [Proc#curry](https://ruby-doc.org/core-2.7.0/Proc.html#method-i-curry)).
99
-
100
- ### `memoize => Proc`
101
-
102
- Returns a memoized proc, that is, a proc that caches it's return values by it's arguments.
103
-
104
- ### `<<(invokable) => Proc`
105
-
106
- Returns a proc that is a composition of this invokable and the given invokable.
107
-
108
- ### `>>(invokable) => Proc`
109
-
110
- Returns a proc that is a composition of this invokable and the given invokable.
111
-
112
154
  ## See Also
113
155
 
156
+ - [Closures and Objects are Equivalent](http://wiki.c2.com/?ClosuresAndObjectsAreEquivalent)
114
157
  - [Clojure](https://clojure.org)
115
158
  - [Arc](http://www.arclanguage.org)
116
159
 
117
- ## TODO
118
-
119
- - benchmark Invokable#to_proc
120
-
121
160
  ## License
122
161
 
123
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,15 +1,82 @@
1
1
  require 'invokable/version'
2
2
  require 'invokable/core'
3
- require 'invokable/compose'
4
-
5
- if RUBY_VERSION.split('.').take(2).join('.').to_f < 2.6
6
- require 'invokable/proc'
7
- require 'invokable/method'
8
- end
3
+ require 'invokable/helpers'
4
+ require 'core_ext'
9
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 ...>
10
28
  module Invokable
29
+ extend Invokable::Helpers
30
+
11
31
  def self.included(base)
12
- base.include(Invokable::Core)
13
- base.include(Invokable::Compose)
32
+ INCLUDED_MODULES.each do |mod|
33
+ base.include(mod)
34
+ base.extend(mod)
35
+ end
36
+ base.extend(ClassMethods)
37
+ end
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.
46
+ module ClassMethods
47
+ # Return the "total" arity of the class (i.e. the arity of the initializer and the arity of the call method)
48
+ #
49
+ # @version 0.6.0
50
+ #
51
+ # @return [Integer]
52
+ def arity
53
+ initializer_arity + instance_method(:call).arity
54
+ end
55
+
56
+ # Handle automatic currying--will accept either the initializer arity or the total arity of the class. If
57
+ # the initializer arity is used return a class instance. If the total arity is used instantiate the class
58
+ # and return the results of the `call` method.
59
+ #
60
+ # @version 0.6.0
61
+ #
62
+ # @see arity
63
+ def call(*args)
64
+ return new.call if arity == 0
65
+ return new(*args) if args.length == initializer_arity
66
+
67
+ if args.length == arity
68
+ init_args = args.slice(0, initializer_arity)
69
+ call_args = args.slice(initializer_arity, args.length)
70
+ new(*init_args).call(*call_args)
71
+ else
72
+ raise ArgumentError, "wrong number of arguments (given #{args.length}, expected #{initializer_arity} or #{arity})"
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def initializer_arity
79
+ instance_method(:initialize).arity
80
+ end
14
81
  end
15
82
  end
@@ -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
@@ -0,0 +1,114 @@
1
+ module Invokable
2
+ # Treat classes as curried functions
3
+ #
4
+ # @version 0.5.2
5
+ #
6
+ # @deprecated These features are included in the module {Invokable} by default now.
7
+ module Closure
8
+ def self.included(klass)
9
+ klass.include(Invokable)
10
+ klass.extend(Invokable::Core)
11
+ klass.extend(Invokable::Compose)
12
+ klass.extend(ClassMethods)
13
+ end
14
+
15
+ module ClassMethods
16
+ # Return the "total" arity of the class (i.e. the arity of the initializer and the arity of the call method)
17
+ #
18
+ # @version 0.5.0
19
+ # @see https://ruby-doc.org/core-2.7.1/Proc.html#method-i-arity Proc#arity
20
+ # @see initializer_arity
21
+ #
22
+ # @return [Integer]
23
+ def arity
24
+ initializer_arity + instance_method(:call).arity
25
+ end
26
+
27
+ # Return the arity of the initializer
28
+ #
29
+ # @version 0.5.0
30
+ # @see arity
31
+ #
32
+ # @return [Integer]
33
+ def initializer_arity
34
+ instance_method(:initialize).arity
35
+ end
36
+
37
+ # Handle automatic currying--will accept either the initializer arity or the total arity of the class. If
38
+ # the initializer arity is used return a class instance. If the total arity is used instantiate the class
39
+ # and return the results of the `call` method.
40
+ #
41
+ # @version 0.5.0
42
+ # @see arity
43
+ # @see initializer_arity
44
+ def call(*args)
45
+ if args.length == initializer_arity
46
+ new(*args)
47
+ elsif args.length == arity
48
+ init_args = args.slice(0, initializer_arity)
49
+ call_args = args.slice(initializer_arity, args.length)
50
+ new(*init_args).call(*call_args)
51
+ else
52
+ raise ArgumentError, "wrong number of arguments (given #{args.length}, expected #{initializer_arity} or #{arity})"
53
+ end
54
+ end
55
+
56
+ # Specify any enclosed state with a block or named attributes
57
+ #
58
+ # @example
59
+ # class TwitterPater
60
+ # include Invokable::Command
61
+ #
62
+ # enclose :api_key
63
+ #
64
+ # def call(user)
65
+ # # interact with twitter, return results
66
+ # end
67
+ # end
68
+ #
69
+ # TwitterPater.new(API_KEY).call(User.find(1))
70
+ # TwitterPater.new(API_KEY).api_key == API_KEY # => true
71
+ #
72
+ # class TwitterPater
73
+ # include Invokable::Command
74
+ #
75
+ # enclose do |api_key|
76
+ # @api_key = api_key
77
+ # end
78
+ #
79
+ # def call(user)
80
+ # # interact with twitter, return results
81
+ # end
82
+ # end
83
+ #
84
+ # TwitterPater.new(API_KEY).call(User.find(1))
85
+ # TwitterPater.new(API_KEY).api_key # error 'method' missing
86
+ def enclose(*names, &block)
87
+ return define_initializer_with_block(block) unless block.nil?
88
+
89
+ define_initializer_with_names(names)
90
+ end
91
+
92
+ private
93
+
94
+ def define_initializer_with_block(block)
95
+ @initializer = block
96
+ define_method :initialize, &block
97
+ end
98
+
99
+ def define_initializer_with_names(names)
100
+ @initializer_arity = names.length
101
+
102
+ names.each do |name|
103
+ attr_reader name
104
+ end
105
+
106
+ define_method :initialize do |*args|
107
+ names.each_with_index do |name, i|
108
+ instance_variable_set(:"@#{name}", args[i])
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,17 @@
1
+ require_relative 'closure'
2
+
3
+ module Invokable
4
+ # Treat "Command Objects" as curried functions
5
+ #
6
+ # @version 0.5.0
7
+ #
8
+ # @deprecated These features are included in the {Invokable} module by default now.
9
+ module Command
10
+ def self.included(klass)
11
+ klass.include(Invokable)
12
+ klass.extend(Invokable::Core)
13
+ klass.extend(Invokable::Compose)
14
+ klass.extend(Invokable::Closure::ClassMethods)
15
+ end
16
+ end
17
+ end
@@ -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,10 +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
40
+
41
+ # Return the arity (i.e. the number of arguments) of the "call" method.
42
+ #
43
+ # @version 0.5.0
44
+ #
45
+ # @return [Integer]
46
+ def arity
47
+ method(:call).arity
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
39
64
  end
40
65
  end
@@ -1,3 +1,3 @@
1
- require_relative 'core'
2
1
  require_relative 'hash'
3
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.4.2"
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.4.2
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delon Newman
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-19 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,14 +70,16 @@ 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
76
+ - lib/invokable/closure.rb
77
+ - lib/invokable/command.rb
75
78
  - lib/invokable/compose.rb
76
79
  - lib/invokable/core.rb
77
80
  - lib/invokable/data.rb
78
81
  - lib/invokable/hash.rb
79
- - lib/invokable/method.rb
80
- - lib/invokable/proc.rb
82
+ - lib/invokable/helpers.rb
81
83
  - lib/invokable/set.rb
82
84
  - lib/invokable/version.rb
83
85
  homepage: https://github.com/delonnewman/invokable
@@ -89,7 +91,7 @@ metadata:
89
91
  source_code_uri: https://github.com/delonnewman/invokable
90
92
  changelog_uri: https://github.com/delonnewman/invokable#changelog
91
93
  documentation_uri: https://www.rubydoc.info/gems/invokable
92
- post_install_message:
94
+ post_install_message:
93
95
  rdoc_options: []
94
96
  require_paths:
95
97
  - lib
@@ -104,8 +106,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
104
106
  - !ruby/object:Gem::Version
105
107
  version: '0'
106
108
  requirements: []
107
- rubygems_version: 3.0.6
108
- signing_key:
109
+ rubygems_version: 3.0.8
110
+ signing_key:
109
111
  specification_version: 4
110
112
  summary: Objects are functions! Treat any Object, Hashes, Arrays and Sets as Procs
111
113
  (like Enumerable but for Proc-like objects)
@@ -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