tins 1.32.0 → 1.44.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/.contexts/code_comment.rb +23 -0
- data/.contexts/full.rb +31 -0
- data/.contexts/lib.rb +24 -0
- data/.contexts/yard.md +92 -0
- data/.github/workflows/codeql-analysis.yml +72 -0
- data/CHANGES.md +194 -0
- data/README.md +161 -90
- data/Rakefile +23 -19
- data/examples/let.rb +8 -40
- data/examples/mail.rb +0 -1
- data/examples/ones_difference.stm +0 -1
- data/examples/turing.rb +3 -1
- data/lib/tins/alias.rb +1 -0
- data/lib/tins/annotate.rb +37 -27
- data/lib/tins/ask_and_send.rb +41 -0
- data/lib/tins/attempt.rb +39 -0
- data/lib/tins/bijection.rb +34 -0
- data/lib/tins/case_predicate.rb +21 -0
- data/lib/tins/complete.rb +16 -0
- data/lib/tins/concern.rb +100 -0
- data/lib/tins/date_dummy.rb +36 -4
- data/lib/tins/date_time_dummy.rb +34 -2
- data/lib/tins/deep_dup.rb +9 -2
- data/lib/tins/deprecate.rb +27 -0
- data/lib/tins/dslkit.rb +563 -59
- data/lib/tins/duration.rb +160 -3
- data/lib/tins/expose.rb +54 -5
- data/lib/tins/extract_last_argument_options.rb +9 -0
- data/lib/tins/file_binary.rb +108 -25
- data/lib/tins/find.rb +114 -11
- data/lib/tins/generator.rb +10 -2
- data/lib/tins/go.rb +81 -4
- data/lib/tins/hash_bfs.rb +69 -0
- data/lib/tins/hash_symbolize_keys_recursive.rb +62 -4
- data/lib/tins/hash_union.rb +47 -2
- data/lib/tins/if_predicate.rb +31 -0
- data/lib/tins/implement.rb +50 -0
- data/lib/tins/limited.rb +105 -29
- data/lib/tins/lines_file.rb +81 -2
- data/lib/tins/lru_cache.rb +54 -17
- data/lib/tins/memoize.rb +86 -58
- data/lib/tins/method_description.rb +87 -4
- data/lib/tins/minimize.rb +39 -11
- data/lib/tins/module_group.rb +27 -2
- data/lib/tins/named_set.rb +20 -0
- data/lib/tins/null.rb +86 -15
- data/lib/tins/once.rb +61 -4
- data/lib/tins/p.rb +44 -8
- data/lib/tins/partial_application.rb +66 -7
- data/lib/tins/proc_compose.rb +58 -1
- data/lib/tins/proc_prelude.rb +97 -10
- data/lib/tins/range_plus.rb +30 -2
- data/lib/tins/require_maybe.rb +36 -0
- data/lib/tins/responding.rb +39 -0
- data/lib/tins/secure_write.rb +25 -5
- data/lib/tins/sexy_singleton.rb +46 -48
- data/lib/tins/string_byte_order_mark.rb +33 -2
- data/lib/tins/string_camelize.rb +31 -2
- data/lib/tins/string_named_placeholders.rb +70 -0
- data/lib/tins/string_underscore.rb +33 -2
- data/lib/tins/string_version.rb +183 -10
- data/lib/tins/subhash.rb +35 -10
- data/lib/tins/temp_io.rb +7 -0
- data/lib/tins/temp_io_enum.rb +19 -0
- data/lib/tins/terminal.rb +34 -12
- data/lib/tins/thread_local.rb +69 -11
- data/lib/tins/time_dummy.rb +47 -21
- data/lib/tins/to.rb +15 -0
- data/lib/tins/to_proc.rb +17 -4
- data/lib/tins/token.rb +61 -2
- data/lib/tins/unit.rb +288 -149
- data/lib/tins/version.rb +1 -1
- data/lib/tins/write.rb +14 -3
- data/lib/tins/xt/blank.rb +81 -2
- data/lib/tins/xt/concern.rb +51 -0
- data/lib/tins/xt/deep_dup.rb +4 -2
- data/lib/tins/xt/deprecate.rb +5 -0
- data/lib/tins/xt/full.rb +56 -11
- data/lib/tins/xt/hash_bfs.rb +7 -0
- data/lib/tins/xt/irb.rb +46 -2
- data/lib/tins/xt/method_description.rb +0 -12
- data/lib/tins/xt/minimize.rb +7 -0
- data/lib/tins/xt/named.rb +71 -16
- data/lib/tins/xt/proc_compose.rb +4 -0
- data/lib/tins/xt/secure_write.rb +0 -4
- data/lib/tins/xt/string.rb +1 -0
- data/lib/tins/xt/string_camelize.rb +4 -2
- data/lib/tins/xt/string_named_placeholders.rb +7 -0
- data/lib/tins/xt/string_underscore.rb +4 -2
- data/lib/tins/xt/subhash.rb +11 -0
- data/lib/tins/xt/time_freezer.rb +43 -6
- data/lib/tins/xt/write.rb +0 -4
- data/lib/tins/xt.rb +3 -3
- data/lib/tins.rb +19 -3
- data/tests/annotate_test.rb +0 -1
- data/tests/bijection_test.rb +0 -1
- data/tests/concern_test.rb +63 -4
- data/tests/date_dummy_test.rb +0 -1
- data/tests/date_time_dummy_test.rb +0 -1
- data/tests/delegate_test.rb +0 -1
- data/tests/deprecate_test.rb +41 -0
- data/tests/dslkit_test.rb +15 -1
- data/tests/duration_test.rb +23 -2
- data/tests/dynamic_scope_test.rb +0 -1
- data/tests/extract_last_argument_options_test.rb +0 -1
- data/tests/find_test.rb +0 -1
- data/tests/from_module_test.rb +30 -3
- data/tests/generator_test.rb +0 -1
- data/tests/go_test.rb +0 -1
- data/tests/hash_bfs_test.rb +34 -0
- data/tests/hash_symbolize_keys_recursive_test.rb +0 -1
- data/tests/implement_test.rb +6 -9
- data/tests/limited_test.rb +12 -12
- data/tests/lines_file_test.rb +2 -1
- data/tests/lru_cache_test.rb +12 -1
- data/tests/memoize_test.rb +0 -1
- data/tests/method_description_test.rb +14 -20
- data/tests/minimize_test.rb +0 -1
- data/tests/module_group_test.rb +0 -1
- data/tests/named_set_test.rb +0 -1
- data/tests/null_test.rb +0 -1
- data/tests/partial_application_test.rb +4 -0
- data/tests/proc_prelude_test.rb +1 -1
- data/tests/require_maybe_test.rb +0 -1
- data/tests/scope_test.rb +1 -2
- data/tests/secure_write_test.rb +6 -1
- data/tests/sexy_singleton_test.rb +1 -1
- data/tests/string_named_placeholders.rb +109 -0
- data/tests/string_version_test.rb +3 -1
- data/tests/subhash_test.rb +0 -1
- data/tests/test_helper.rb +4 -7
- data/tests/time_dummy_test.rb +0 -1
- data/tests/time_freezer_test.rb +1 -1
- data/tests/to_test.rb +6 -6
- data/tests/token_test.rb +0 -1
- data/tests/unit_test.rb +0 -1
- data/tins.gemspec +18 -30
- metadata +55 -38
- data/lib/tins/count_by.rb +0 -8
- data/lib/tins/deep_const_get.rb +0 -50
- data/lib/tins/timed_cache.rb +0 -51
- data/lib/tins/uniq_by.rb +0 -10
- data/lib/tins/xt/count_by.rb +0 -11
- data/lib/tins/xt/deep_const_get.rb +0 -7
- data/lib/tins/xt/uniq_by.rb +0 -15
- data/tests/count_by_test.rb +0 -17
- data/tests/deep_const_get_test.rb +0 -37
- data/tests/uniq_by_test.rb +0 -31
- /data/{COPYING → LICENSE} +0 -0
data/lib/tins/null.rb
CHANGED
@@ -1,68 +1,131 @@
|
|
1
1
|
module Tins
|
2
|
-
#
|
2
|
+
# Tins::Null provides an implementation of the null object pattern in Ruby.
|
3
|
+
#
|
4
|
+
# The null object pattern is a behavioral design pattern that allows you to
|
5
|
+
# avoid null references by providing a default object that implements the
|
6
|
+
# expected interface but does nothing. This eliminates the need for null
|
7
|
+
# checks throughout your codebase.
|
8
|
+
#
|
9
|
+
# This module provides the core functionality for null objects, including:
|
10
|
+
# - Method missing behavior that returns self
|
11
|
+
# - Type conversion methods that return appropriate default values
|
12
|
+
# - Debugging support through NullPlus
|
13
|
+
#
|
14
|
+
# @example Basic usage
|
15
|
+
# # Instead of checking for nil:
|
16
|
+
# user = find_user(id)
|
17
|
+
# if user
|
18
|
+
# user.name
|
19
|
+
# else
|
20
|
+
# "Unknown"
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# # You can use the null object:
|
24
|
+
# user = find_user(id) || Tins::NULL
|
25
|
+
# user.name # => "" (instead of nil)
|
26
|
+
#
|
27
|
+
# @example With NullPlus for debugging
|
28
|
+
# user = find_user(id) || Tins::NullPlus.new(value: "Unknown", caller: caller)
|
29
|
+
# user.value # => "Unknown"
|
3
30
|
module Null
|
31
|
+
# Handle missing methods by returning self, allowing method chaining.
|
32
|
+
#
|
33
|
+
# @return [self] Always returns self to allow chaining
|
4
34
|
def method_missing(*)
|
5
35
|
self
|
6
36
|
end
|
7
37
|
|
38
|
+
# Handle missing constants by returning self.
|
39
|
+
#
|
40
|
+
# @return [self] Always returns self
|
8
41
|
def const_missing(*)
|
9
42
|
self
|
10
43
|
end
|
11
44
|
|
45
|
+
# Convert to string representation.
|
46
|
+
#
|
47
|
+
# @return [String] Empty string
|
12
48
|
def to_s
|
13
49
|
''
|
14
50
|
end
|
15
51
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
52
|
+
# Convert to float.
|
53
|
+
#
|
54
|
+
# @return [Float] Zero as float
|
20
55
|
def to_f
|
21
56
|
0.0
|
22
57
|
end
|
23
58
|
|
59
|
+
# Convert to integer.
|
60
|
+
#
|
61
|
+
# @return [Integer] Zero
|
24
62
|
def to_i
|
25
63
|
0
|
26
64
|
end
|
27
65
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
66
|
+
# Convert to array.
|
67
|
+
#
|
68
|
+
# @return [Array] Empty array
|
32
69
|
def to_a
|
33
70
|
[]
|
34
71
|
end
|
35
72
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
73
|
+
# Inspect representation.
|
74
|
+
#
|
75
|
+
# @return [String] "NULL"
|
40
76
|
def inspect
|
41
77
|
'NULL'
|
42
78
|
end
|
43
79
|
|
80
|
+
# Check if object is nil.
|
81
|
+
#
|
82
|
+
# @return [Boolean] Always returns true
|
44
83
|
def nil?
|
45
84
|
true
|
46
85
|
end
|
47
86
|
|
87
|
+
# Check if object is blank.
|
88
|
+
#
|
89
|
+
# @return [Boolean] Always returns true
|
48
90
|
def blank?
|
49
91
|
true
|
50
92
|
end
|
51
93
|
|
94
|
+
# Convert to JSON (for compatibility with JSON serialization).
|
95
|
+
#
|
96
|
+
# @return [nil] returns nil value
|
52
97
|
def as_json(*)
|
98
|
+
nil
|
53
99
|
end
|
54
100
|
|
101
|
+
# Convert to JSON string.
|
102
|
+
#
|
103
|
+
# @return [String] "null"
|
55
104
|
def to_json(*)
|
56
105
|
'null'
|
57
106
|
end
|
58
107
|
|
108
|
+
# Kernel extensions for null object creation.
|
59
109
|
module Kernel
|
110
|
+
# Create a null object or return the provided value if not nil.
|
111
|
+
#
|
112
|
+
# @param value [Object] The value to check
|
113
|
+
# @return [Object] Tins::NULL if value is nil, otherwise value
|
60
114
|
def null(value = nil)
|
61
115
|
value.nil? ? Tins::NULL : value
|
62
116
|
end
|
63
117
|
|
64
118
|
alias Null null
|
65
119
|
|
120
|
+
# Create a null object with additional debugging information.
|
121
|
+
#
|
122
|
+
# This creates a NullPlus object that includes caller information for
|
123
|
+
# debugging purposes when the null object is used.
|
124
|
+
#
|
125
|
+
# @param opts [Hash] Options for the null object
|
126
|
+
# @option opts [Object] :value The value to return from the null object
|
127
|
+
# @option opts [Array] :caller Caller information
|
128
|
+
# @return [Object] Tins::NullPlus if value is nil, otherwise value
|
66
129
|
def null_plus(opts = {})
|
67
130
|
value = opts[:value]
|
68
131
|
opts[:caller] = caller
|
@@ -77,17 +140,27 @@ module Tins
|
|
77
140
|
end
|
78
141
|
end
|
79
142
|
|
143
|
+
# NullClass represents the singleton null object instance.
|
80
144
|
class NullClass < Module
|
81
145
|
include Tins::Null
|
82
146
|
end
|
83
147
|
|
148
|
+
# The singleton null object instance.
|
84
149
|
NULL = NullClass.new
|
85
150
|
|
151
|
+
# Freeze the singleton to prevent modification.
|
86
152
|
NULL.freeze
|
87
153
|
|
154
|
+
# Enhanced null object with debugging capabilities.
|
155
|
+
#
|
156
|
+
# NullPlus extends the basic null object with additional features for debugging,
|
157
|
+
# including caller information and custom attribute access.
|
88
158
|
class NullPlus
|
89
159
|
include Tins::Null
|
90
160
|
|
161
|
+
# Initialize a NullPlus object with options.
|
162
|
+
#
|
163
|
+
# @param opts [Hash] Configuration options
|
91
164
|
def initialize(opts = {})
|
92
165
|
singleton_class.class_eval do
|
93
166
|
opts.each do |name, value|
|
@@ -97,5 +170,3 @@ module Tins
|
|
97
170
|
end
|
98
171
|
end
|
99
172
|
end
|
100
|
-
|
101
|
-
require 'tins/alias'
|
data/lib/tins/once.rb
CHANGED
@@ -1,9 +1,52 @@
|
|
1
1
|
module Tins
|
2
|
+
# A module for ensuring exclusive execution of code blocks using file-based
|
3
|
+
# locking.
|
4
|
+
#
|
5
|
+
# This module provides mechanisms to prevent multiple instances of a script
|
6
|
+
# from running simultaneously by using file system locks on the script itself
|
7
|
+
# or a specified lock file.
|
8
|
+
#
|
9
|
+
# @example Basic usage with automatic lock file detection
|
10
|
+
# Tins::Once.only_once do
|
11
|
+
# # Critical section - only one instance runs at a time
|
12
|
+
# perform_backup()
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# @example Using custom lock file
|
16
|
+
# Tins::Once.only_once("/var/run/myapp.lock") do
|
17
|
+
# # Custom lock file
|
18
|
+
# process_data()
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# @example Non-blocking attempt
|
22
|
+
# begin
|
23
|
+
# Tins::Once.try_only_once do
|
24
|
+
# # Will raise if another instance holds the lock
|
25
|
+
# update_cache()
|
26
|
+
# end
|
27
|
+
# rescue => e
|
28
|
+
# puts "Another instance is running"
|
29
|
+
# end
|
2
30
|
module Once
|
3
31
|
include File::Constants
|
4
32
|
|
5
|
-
|
6
|
-
|
33
|
+
# Executes a block of code exclusively, ensuring only one instance runs at
|
34
|
+
# a time.
|
35
|
+
#
|
36
|
+
# Uses the script name (or specified lock file) as the locking mechanism.
|
37
|
+
# The first invocation will acquire an exclusive lock and execute the
|
38
|
+
# block, while subsequent invocations will block until the lock is
|
39
|
+
# released.
|
40
|
+
#
|
41
|
+
# @param lock_filename [String] Optional custom lock filename.
|
42
|
+
# Defaults to `$0` (the script name), which means the script itself
|
43
|
+
# is used as the lock file.
|
44
|
+
# @param locking_constant [Integer] File locking constant.
|
45
|
+
# Defaults to `LOCK_EX` for exclusive locking.
|
46
|
+
# @yield [void] The block of code to execute exclusively
|
47
|
+
# @return [Object] The return value of the yielded block
|
48
|
+
# @raise [Errno::ENOENT] If the lock file doesn't exist
|
49
|
+
# @raise [SystemCallError] If file locking fails
|
7
50
|
def only_once(lock_filename = nil, locking_constant = nil)
|
8
51
|
lock_filename ||= $0
|
9
52
|
locking_constant ||= LOCK_EX
|
@@ -16,10 +59,24 @@ module Tins
|
|
16
59
|
end
|
17
60
|
end
|
18
61
|
|
62
|
+
# Attempts to execute a block of code exclusively, but fails immediately
|
63
|
+
# if another instance holds the lock.
|
64
|
+
#
|
65
|
+
# This is a non-blocking version that will raise an exception if the lock
|
66
|
+
# cannot be acquired immediately.
|
67
|
+
#
|
68
|
+
# @param lock_filename [String] Optional custom lock filename.
|
69
|
+
# Defaults to `$0` (the script name).
|
70
|
+
# @param locking_constant [Integer] File locking constant.
|
71
|
+
# Defaults to `LOCK_EX | LOCK_NB` for non-blocking exclusive locking.
|
72
|
+
# @yield [void] The block of code to execute exclusively
|
73
|
+
# @return [Object] The return value of the yielded block
|
74
|
+
# @raise [Errno::EAGAIN] If another process holds the lock
|
75
|
+
# @raise [Errno::ENOENT] If the lock file doesn't exist
|
19
76
|
def try_only_once(lock_filename = nil, locking_constant = nil, &block)
|
20
77
|
only_once(lock_filename, locking_constant || LOCK_EX | LOCK_NB, &block)
|
21
78
|
end
|
79
|
+
|
80
|
+
module_function :only_once, :try_only_once
|
22
81
|
end
|
23
82
|
end
|
24
|
-
|
25
|
-
require 'tins/alias'
|
data/lib/tins/p.rb
CHANGED
@@ -1,23 +1,59 @@
|
|
1
1
|
require 'pp'
|
2
2
|
|
3
3
|
module Tins
|
4
|
+
# A module that provides debugging methods for inspecting objects by raising
|
5
|
+
# exceptions with their inspected representations.
|
6
|
+
#
|
7
|
+
# This module adds p! and pp! methods that raise RuntimeError exceptions
|
8
|
+
# containing the inspected or pretty-inspected output of objects, making it
|
9
|
+
# easy to quickly debug values during development without printing to stdout.
|
10
|
+
#
|
11
|
+
# @example Using p! to inspect a single object
|
12
|
+
# p!(some_variable)
|
13
|
+
#
|
14
|
+
# @example Using pp! to inspect multiple objects
|
15
|
+
# pp!(first_var, second_var)
|
4
16
|
module P
|
5
17
|
private
|
6
18
|
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
19
|
+
# Raises a RuntimeError with the inspected representation of the given
|
20
|
+
# objects.
|
21
|
+
#
|
22
|
+
# This method is useful for quick debugging by raising an exception that
|
23
|
+
# contains the inspected output of the provided objects. It behaves
|
24
|
+
# similarly to Ruby's built-in +p+ method but raises an exception instead
|
25
|
+
# of printing to stdout.
|
26
|
+
#
|
27
|
+
# @example Basic usage with single object
|
28
|
+
# p!(some_variable)
|
29
|
+
#
|
30
|
+
# @example Basic usage with multiple objects
|
31
|
+
# p!(first_var, second_var)
|
32
|
+
#
|
33
|
+
# @param objs [Array<Object>] One or more objects to inspect and raise
|
34
|
+
# @raise [RuntimeError] Always raises a RuntimeError with inspected content
|
10
35
|
def p!(*objs)
|
11
36
|
raise((objs.size < 2 ? objs.first : objs).inspect)
|
12
37
|
end
|
13
38
|
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
39
|
+
# Raises a RuntimeError with the pretty-inspected representation of the
|
40
|
+
# given objects.
|
41
|
+
#
|
42
|
+
# This method is useful for quick debugging by raising an exception that
|
43
|
+
# contains the pretty-printed output of the provided objects. It behaves
|
44
|
+
# similarly to Ruby's built-in +pp+ method but raises an exception instead
|
45
|
+
# of printing to stdout.
|
46
|
+
#
|
47
|
+
# @example Basic usage with single object
|
48
|
+
# pp!(some_variable)
|
49
|
+
#
|
50
|
+
# @example Basic usage with multiple objects
|
51
|
+
# pp!(first_var, second_var)
|
52
|
+
#
|
53
|
+
# @param objs [Array<Object>] One or more objects to pretty-inspect and raise
|
54
|
+
# @raise [RuntimeError] Always raises a RuntimeError with pretty-inspected content
|
17
55
|
def pp!(*objs)
|
18
56
|
raise("\n" + (objs.size < 2 ? objs.first : objs).pretty_inspect.chomp)
|
19
57
|
end
|
20
58
|
end
|
21
59
|
end
|
22
|
-
|
23
|
-
require 'tins/alias'
|
@@ -1,19 +1,80 @@
|
|
1
1
|
module Tins
|
2
|
+
# A module that provides partial application functionality.
|
3
|
+
#
|
4
|
+
# This module is designed to be included in classes that respond to `call` and have
|
5
|
+
# an `arity` method. It's commonly used with Proc and Method objects, but can be
|
6
|
+
# included in any class that implements the required interface.
|
7
|
+
#
|
8
|
+
# Partial application allows you to create new callables by fixing some arguments
|
9
|
+
# of an existing callable, resulting in a callable with fewer parameters.
|
10
|
+
#
|
11
|
+
# @example Using partial application with Proc
|
12
|
+
# add = proc { |x, y| x + y }
|
13
|
+
# add_five = add.partial(5) # Fixes first argument to 5
|
14
|
+
# result = add_five.call(3) # Returns 8 (5 + 3)
|
15
|
+
#
|
16
|
+
# @example Using partial application with Method
|
17
|
+
# class Calculator
|
18
|
+
# def calculate(x, y, z)
|
19
|
+
# x + y * z
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# calc = Calculator.new
|
24
|
+
# method_obj = calc.method(:calculate)
|
25
|
+
# partial_calc = method_obj.partial(1, 2) # Fixes first two arguments
|
26
|
+
# result = partial_calc.call(3) # Returns 7 (1 + 2 * 3)
|
2
27
|
module PartialApplication
|
3
|
-
#
|
4
|
-
#
|
28
|
+
# Callback invoked when this module is included in a class. Overrides the
|
29
|
+
# `arity` method to support custom arity values for partial applications.
|
30
|
+
#
|
31
|
+
# This is particularly useful for Proc and Method objects where the arity
|
32
|
+
# needs to be adjusted after partial application.
|
33
|
+
#
|
34
|
+
# @param modul [Module] The module that included this module
|
5
35
|
def self.included(modul)
|
6
36
|
modul.module_eval do
|
7
37
|
old_arity = instance_method(:arity)
|
8
38
|
define_method(:arity) do
|
9
|
-
defined?(@__arity__)
|
39
|
+
if defined?(@__arity__)
|
40
|
+
@__arity__
|
41
|
+
else
|
42
|
+
old_arity.bind(self).call
|
43
|
+
end
|
10
44
|
end
|
11
45
|
end
|
12
46
|
super
|
13
47
|
end
|
14
48
|
|
15
|
-
#
|
16
|
-
#
|
49
|
+
# Creates a partial application of the current object.
|
50
|
+
#
|
51
|
+
# If no arguments are provided, returns a duplicate of the current object.
|
52
|
+
# If more arguments are provided than the object's arity, raises an
|
53
|
+
# ArgumentError. Otherwise, creates a new lambda that combines the provided
|
54
|
+
# arguments with additional arguments when called.
|
55
|
+
#
|
56
|
+
# This method is particularly useful for creating curried functions or
|
57
|
+
# partially applied methods where some parameters are pre-filled.
|
58
|
+
#
|
59
|
+
# @param args [Array] Arguments to partially apply to the callable
|
60
|
+
# @return [Proc] A partial application of this object with adjusted arity
|
61
|
+
# @raise [ArgumentError] If too many arguments are provided for the arity
|
62
|
+
# @example
|
63
|
+
# add = proc { |x, y| x + y }
|
64
|
+
# add_five = add.partial(5)
|
65
|
+
# add_five.call(3) # => 8
|
66
|
+
#
|
67
|
+
# @example With Method objects
|
68
|
+
# class MathOps
|
69
|
+
# def multiply(a, b, c)
|
70
|
+
# a * b * c
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# ops = MathOps.new
|
75
|
+
# method_obj = ops.method(:multiply)
|
76
|
+
# partial_mult = method_obj.partial(2, 3)
|
77
|
+
# partial_mult.call(4) # => 24 (2 * 3 * 4)
|
17
78
|
def partial(*args)
|
18
79
|
if args.empty?
|
19
80
|
dup
|
@@ -27,5 +88,3 @@ module Tins
|
|
27
88
|
end
|
28
89
|
end
|
29
90
|
end
|
30
|
-
|
31
|
-
require 'tins/alias'
|
data/lib/tins/proc_compose.rb
CHANGED
@@ -1,15 +1,72 @@
|
|
1
1
|
module Tins
|
2
|
+
# A module that provides function composition functionality for Proc objects.
|
3
|
+
#
|
4
|
+
# This module enables the composition of two functions (procs) such that the
|
5
|
+
# result is a new proc that applies the second function to the input, then
|
6
|
+
# applies the first function to the result. This follows the mathematical
|
7
|
+
# concept of function composition: (f ∘ g)(x) = f(g(x))
|
8
|
+
#
|
9
|
+
# @example Basic composition
|
10
|
+
# add_one = proc { |x| x + 1 }
|
11
|
+
# multiply_by_two = proc { |x| x * 2 }
|
12
|
+
#
|
13
|
+
# composed = multiply_by_two.compose(add_one)
|
14
|
+
# composed.call(5) # => 12 (2 * (5 + 1))
|
15
|
+
#
|
16
|
+
# @example Using the alias operator
|
17
|
+
# add_one = proc { |x| x + 1 }
|
18
|
+
# multiply_by_two = proc { |x| x * 2 }
|
19
|
+
#
|
20
|
+
# composed = multiply_by_two * add_one
|
21
|
+
# composed.call(5) # => 12
|
2
22
|
module ProcCompose
|
23
|
+
# Composes this proc with another callable, creating a new proc that
|
24
|
+
# applies the other callable first, then applies this proc to its result.
|
25
|
+
#
|
26
|
+
# The composition follows the mathematical convention:
|
27
|
+
#
|
28
|
+
# (self ∘ other)(args) = self(other(args))
|
29
|
+
#
|
30
|
+
# @param other [Proc, Method, Object] A callable object that responds to `call`
|
31
|
+
# or can be converted to a proc via `to_proc`
|
32
|
+
# @return [Proc] A new proc representing the composition
|
33
|
+
# @example
|
34
|
+
# square = proc { |x| x * x }
|
35
|
+
# add_one = proc { |x| x + 1 }
|
36
|
+
#
|
37
|
+
# composed = square.compose(add_one)
|
38
|
+
# composed.call(3) # => 16 ((3 + 1)²)
|
39
|
+
#
|
40
|
+
# @example With Method objects
|
41
|
+
# class MathOps
|
42
|
+
# def square(x)
|
43
|
+
# x * x
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# math = MathOps.new
|
48
|
+
# add_one = proc { |x| x + 1 }
|
49
|
+
# composed = math.method(:square).compose(add_one)
|
50
|
+
# composed.call(3) # => 16 ((3 + 1)²)
|
3
51
|
def compose(other)
|
4
|
-
|
52
|
+
block = -> *args {
|
5
53
|
if other.respond_to?(:call)
|
6
54
|
call(*other.call(*args))
|
7
55
|
else
|
8
56
|
call(*other.to_proc.call(*args))
|
9
57
|
end
|
58
|
+
}
|
59
|
+
if self.class.respond_to?(:new)
|
60
|
+
self.class.new(&block)
|
61
|
+
else
|
62
|
+
Proc.new(&block)
|
10
63
|
end
|
11
64
|
end
|
12
65
|
|
66
|
+
# Alias for {compose} method, enabling the use of the * operator for
|
67
|
+
# composition.
|
68
|
+
#
|
69
|
+
# @see compose
|
13
70
|
alias * compose
|
14
71
|
end
|
15
72
|
end
|
data/lib/tins/proc_prelude.rb
CHANGED
@@ -1,71 +1,158 @@
|
|
1
1
|
require 'tins/memoize'
|
2
2
|
|
3
3
|
module Tins
|
4
|
+
# Tins::ProcPrelude provides a set of utility methods for creating and composing
|
5
|
+
# Proc objects with common functional programming patterns.
|
6
|
+
#
|
7
|
+
# This module contains various helper methods that return lambda functions,
|
8
|
+
# making it easier to build complex processing pipelines and functional
|
9
|
+
# transformations. These are particularly useful in functional programming
|
10
|
+
# contexts or when working with higher-order functions.
|
11
|
+
#
|
12
|
+
# The methods are typically accessed through the singleton interface:
|
13
|
+
# Proc.array.(1, 2, 3) # => [1, 2, 3]
|
14
|
+
#
|
15
|
+
# @example Basic usage with map_apply in reduce context
|
16
|
+
# # Create a proc that applies a method to each element and accumulates results
|
17
|
+
# proc = Proc.map_apply(:upcase) { |s, upcased| s << upcased }
|
18
|
+
#
|
19
|
+
# # Use it with Array#reduce
|
20
|
+
# ['hello', 'world'].reduce([], &proc)
|
21
|
+
# # => ['HELLO', 'WORLD']
|
22
|
+
#
|
23
|
+
# @example Using array to convert arguments to list
|
24
|
+
# proc = Proc.array
|
25
|
+
# proc.(1, 2, 3)
|
26
|
+
# # => [1, 2, 3]
|
4
27
|
module ProcPrelude
|
28
|
+
# Create a proc that applies the given block to a list of arguments.
|
29
|
+
#
|
30
|
+
# @yield [list] The block to apply to the arguments
|
31
|
+
# @return [Proc] A proc that takes arguments and calls the block with them unpacked
|
5
32
|
def apply(&my_proc)
|
6
33
|
my_proc or raise ArgumentError, 'a block argument is required'
|
7
|
-
lambda { |list| my_proc.
|
34
|
+
lambda { |list| my_proc.(*list) }
|
8
35
|
end
|
9
36
|
|
37
|
+
# Create a proc that applies a method to an object and then applies the block.
|
38
|
+
#
|
39
|
+
# @param my_method [Symbol] The method name to call on each element
|
40
|
+
# @param args [Array] Additional arguments to pass to the method
|
41
|
+
# @yield [x, y] The block to apply after calling the method
|
42
|
+
# @return [Proc] A proc that takes two arguments and applies the method + block
|
43
|
+
# @raise [ArgumentError] if no block is provided
|
10
44
|
def map_apply(my_method, *args, &my_proc)
|
11
45
|
my_proc or raise ArgumentError, 'a block argument is required'
|
12
|
-
lambda { |x, y| my_proc.
|
46
|
+
lambda { |x, y| my_proc.(x, y.__send__(my_method, *args)) }
|
13
47
|
end
|
14
48
|
|
49
|
+
# Create a proc that evaluates a block in the context of an object.
|
50
|
+
#
|
51
|
+
# @param obj [Object] The object to evaluate the block against
|
52
|
+
# @yield [obj] The block to evaluate in the object's context
|
53
|
+
# @return [Proc] A proc that takes an object and evaluates the block
|
15
54
|
def call(obj, &my_proc)
|
16
55
|
my_proc or raise ArgumentError, 'a block argument is required'
|
17
56
|
obj.instance_eval(&my_proc)
|
18
57
|
end
|
19
58
|
|
59
|
+
# Create a proc that converts all arguments to a list array.
|
60
|
+
#
|
61
|
+
# @return [Proc] A proc that takes any number of arguments and returns them as a list
|
20
62
|
def array
|
21
63
|
lambda { |*list| list }
|
22
64
|
end
|
23
|
-
|
65
|
+
memoize function: :array, freeze: true
|
24
66
|
|
67
|
+
# Create a proc that returns the first element (the head) of a list.
|
68
|
+
#
|
69
|
+
# @return [Proc] A proc that takes a list and returns its first element
|
25
70
|
def first
|
26
71
|
lambda { |*list| list.first }
|
27
72
|
end
|
28
|
-
|
73
|
+
memoize function: :first, freeze: true
|
29
74
|
|
30
75
|
alias head first
|
31
76
|
|
77
|
+
# Create a proc that returns the second element of a list.
|
78
|
+
#
|
79
|
+
# @return [Proc] A proc that takes a list and returns its second element
|
32
80
|
def second
|
33
81
|
lambda { |*list| list[1] }
|
34
82
|
end
|
35
|
-
|
83
|
+
memoize function: :second, freeze: true
|
36
84
|
|
85
|
+
# Create a proc that returns all elements except the first (the tail) from
|
86
|
+
# a list.
|
87
|
+
#
|
88
|
+
# @return [Proc] A proc that takes a list and returns all but the first element
|
37
89
|
def tail
|
38
90
|
lambda { |*list| list[1..-1] }
|
39
91
|
end
|
40
|
-
|
92
|
+
memoize function: :tail, freeze: true
|
41
93
|
|
94
|
+
# Create a proc that returns the last element of a list.
|
95
|
+
#
|
96
|
+
# @return [Proc] A proc that takes a list and returns its last element
|
42
97
|
def last
|
43
98
|
lambda { |*list| list.last }
|
44
99
|
end
|
45
|
-
|
100
|
+
memoize function: :last, freeze: true
|
46
101
|
|
102
|
+
# Create a proc that rotates a list by n positions.
|
103
|
+
#
|
104
|
+
# @param n [Integer] Number of positions to rotate (default: 1)
|
105
|
+
# @return [Proc] A proc that takes a list and returns it rotated
|
47
106
|
def rotate(n = 1)
|
48
107
|
lambda { |*list| list.rotate(n) }
|
49
108
|
end
|
50
109
|
|
51
110
|
alias swap rotate
|
52
111
|
|
112
|
+
# Create a proc that returns its argument unchanged.
|
113
|
+
#
|
114
|
+
# @return [Proc] A proc that takes an element and returns it unchanged
|
53
115
|
def id1
|
54
116
|
lambda { |obj| obj }
|
55
117
|
end
|
56
|
-
|
118
|
+
memoize function: :id1, freeze: true
|
57
119
|
|
120
|
+
# Create a proc that returns a constant value.
|
121
|
+
#
|
122
|
+
# @param konst [Object] The constant value to return (optional)
|
123
|
+
# @yield [konst] Block to compute the constant value if not provided
|
124
|
+
# @return [Proc] A proc that always returns the same value
|
58
125
|
def const(konst = nil, &my_proc)
|
59
|
-
konst ||= my_proc.
|
126
|
+
konst ||= my_proc.()
|
60
127
|
lambda { |*_| konst }
|
61
128
|
end
|
62
129
|
|
130
|
+
# Create a proc that returns the nth element of a list.
|
131
|
+
#
|
132
|
+
# @param n [Integer] The index of the element to return
|
133
|
+
# @return [Proc] A proc that takes a list and returns element at index n
|
63
134
|
def nth(n)
|
64
135
|
lambda { |*list| list[n] }
|
65
136
|
end
|
66
137
|
|
138
|
+
# Create a proc that calls a method on self with given arguments.
|
139
|
+
#
|
140
|
+
# This method uses binding introspection to dynamically determine which
|
141
|
+
# method to call, making it useful for creating flexible function
|
142
|
+
# references.
|
143
|
+
#
|
144
|
+
# @yield [] The block that should return a method name (symbol)
|
145
|
+
# @return [Proc] A proc that takes arguments and calls the specified method
|
146
|
+
# @example Dynamic method invocation
|
147
|
+
# def square(x)
|
148
|
+
# x ** 2
|
149
|
+
# end
|
150
|
+
#
|
151
|
+
# proc = Proc.from { :square }
|
152
|
+
# [1, 2, 3].map(&proc)
|
153
|
+
# # => [1, 4, 9]
|
67
154
|
def from(&block)
|
68
|
-
my_method, binding = block.
|
155
|
+
my_method, binding = block.(), block.binding
|
69
156
|
my_self = eval 'self', binding
|
70
157
|
lambda { |*list| my_self.__send__(my_method, *list) }
|
71
158
|
end
|