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.
Files changed (150) hide show
  1. checksums.yaml +4 -4
  2. data/.contexts/code_comment.rb +23 -0
  3. data/.contexts/full.rb +31 -0
  4. data/.contexts/lib.rb +24 -0
  5. data/.contexts/yard.md +92 -0
  6. data/.github/workflows/codeql-analysis.yml +72 -0
  7. data/CHANGES.md +194 -0
  8. data/README.md +161 -90
  9. data/Rakefile +23 -19
  10. data/examples/let.rb +8 -40
  11. data/examples/mail.rb +0 -1
  12. data/examples/ones_difference.stm +0 -1
  13. data/examples/turing.rb +3 -1
  14. data/lib/tins/alias.rb +1 -0
  15. data/lib/tins/annotate.rb +37 -27
  16. data/lib/tins/ask_and_send.rb +41 -0
  17. data/lib/tins/attempt.rb +39 -0
  18. data/lib/tins/bijection.rb +34 -0
  19. data/lib/tins/case_predicate.rb +21 -0
  20. data/lib/tins/complete.rb +16 -0
  21. data/lib/tins/concern.rb +100 -0
  22. data/lib/tins/date_dummy.rb +36 -4
  23. data/lib/tins/date_time_dummy.rb +34 -2
  24. data/lib/tins/deep_dup.rb +9 -2
  25. data/lib/tins/deprecate.rb +27 -0
  26. data/lib/tins/dslkit.rb +563 -59
  27. data/lib/tins/duration.rb +160 -3
  28. data/lib/tins/expose.rb +54 -5
  29. data/lib/tins/extract_last_argument_options.rb +9 -0
  30. data/lib/tins/file_binary.rb +108 -25
  31. data/lib/tins/find.rb +114 -11
  32. data/lib/tins/generator.rb +10 -2
  33. data/lib/tins/go.rb +81 -4
  34. data/lib/tins/hash_bfs.rb +69 -0
  35. data/lib/tins/hash_symbolize_keys_recursive.rb +62 -4
  36. data/lib/tins/hash_union.rb +47 -2
  37. data/lib/tins/if_predicate.rb +31 -0
  38. data/lib/tins/implement.rb +50 -0
  39. data/lib/tins/limited.rb +105 -29
  40. data/lib/tins/lines_file.rb +81 -2
  41. data/lib/tins/lru_cache.rb +54 -17
  42. data/lib/tins/memoize.rb +86 -58
  43. data/lib/tins/method_description.rb +87 -4
  44. data/lib/tins/minimize.rb +39 -11
  45. data/lib/tins/module_group.rb +27 -2
  46. data/lib/tins/named_set.rb +20 -0
  47. data/lib/tins/null.rb +86 -15
  48. data/lib/tins/once.rb +61 -4
  49. data/lib/tins/p.rb +44 -8
  50. data/lib/tins/partial_application.rb +66 -7
  51. data/lib/tins/proc_compose.rb +58 -1
  52. data/lib/tins/proc_prelude.rb +97 -10
  53. data/lib/tins/range_plus.rb +30 -2
  54. data/lib/tins/require_maybe.rb +36 -0
  55. data/lib/tins/responding.rb +39 -0
  56. data/lib/tins/secure_write.rb +25 -5
  57. data/lib/tins/sexy_singleton.rb +46 -48
  58. data/lib/tins/string_byte_order_mark.rb +33 -2
  59. data/lib/tins/string_camelize.rb +31 -2
  60. data/lib/tins/string_named_placeholders.rb +70 -0
  61. data/lib/tins/string_underscore.rb +33 -2
  62. data/lib/tins/string_version.rb +183 -10
  63. data/lib/tins/subhash.rb +35 -10
  64. data/lib/tins/temp_io.rb +7 -0
  65. data/lib/tins/temp_io_enum.rb +19 -0
  66. data/lib/tins/terminal.rb +34 -12
  67. data/lib/tins/thread_local.rb +69 -11
  68. data/lib/tins/time_dummy.rb +47 -21
  69. data/lib/tins/to.rb +15 -0
  70. data/lib/tins/to_proc.rb +17 -4
  71. data/lib/tins/token.rb +61 -2
  72. data/lib/tins/unit.rb +288 -149
  73. data/lib/tins/version.rb +1 -1
  74. data/lib/tins/write.rb +14 -3
  75. data/lib/tins/xt/blank.rb +81 -2
  76. data/lib/tins/xt/concern.rb +51 -0
  77. data/lib/tins/xt/deep_dup.rb +4 -2
  78. data/lib/tins/xt/deprecate.rb +5 -0
  79. data/lib/tins/xt/full.rb +56 -11
  80. data/lib/tins/xt/hash_bfs.rb +7 -0
  81. data/lib/tins/xt/irb.rb +46 -2
  82. data/lib/tins/xt/method_description.rb +0 -12
  83. data/lib/tins/xt/minimize.rb +7 -0
  84. data/lib/tins/xt/named.rb +71 -16
  85. data/lib/tins/xt/proc_compose.rb +4 -0
  86. data/lib/tins/xt/secure_write.rb +0 -4
  87. data/lib/tins/xt/string.rb +1 -0
  88. data/lib/tins/xt/string_camelize.rb +4 -2
  89. data/lib/tins/xt/string_named_placeholders.rb +7 -0
  90. data/lib/tins/xt/string_underscore.rb +4 -2
  91. data/lib/tins/xt/subhash.rb +11 -0
  92. data/lib/tins/xt/time_freezer.rb +43 -6
  93. data/lib/tins/xt/write.rb +0 -4
  94. data/lib/tins/xt.rb +3 -3
  95. data/lib/tins.rb +19 -3
  96. data/tests/annotate_test.rb +0 -1
  97. data/tests/bijection_test.rb +0 -1
  98. data/tests/concern_test.rb +63 -4
  99. data/tests/date_dummy_test.rb +0 -1
  100. data/tests/date_time_dummy_test.rb +0 -1
  101. data/tests/delegate_test.rb +0 -1
  102. data/tests/deprecate_test.rb +41 -0
  103. data/tests/dslkit_test.rb +15 -1
  104. data/tests/duration_test.rb +23 -2
  105. data/tests/dynamic_scope_test.rb +0 -1
  106. data/tests/extract_last_argument_options_test.rb +0 -1
  107. data/tests/find_test.rb +0 -1
  108. data/tests/from_module_test.rb +30 -3
  109. data/tests/generator_test.rb +0 -1
  110. data/tests/go_test.rb +0 -1
  111. data/tests/hash_bfs_test.rb +34 -0
  112. data/tests/hash_symbolize_keys_recursive_test.rb +0 -1
  113. data/tests/implement_test.rb +6 -9
  114. data/tests/limited_test.rb +12 -12
  115. data/tests/lines_file_test.rb +2 -1
  116. data/tests/lru_cache_test.rb +12 -1
  117. data/tests/memoize_test.rb +0 -1
  118. data/tests/method_description_test.rb +14 -20
  119. data/tests/minimize_test.rb +0 -1
  120. data/tests/module_group_test.rb +0 -1
  121. data/tests/named_set_test.rb +0 -1
  122. data/tests/null_test.rb +0 -1
  123. data/tests/partial_application_test.rb +4 -0
  124. data/tests/proc_prelude_test.rb +1 -1
  125. data/tests/require_maybe_test.rb +0 -1
  126. data/tests/scope_test.rb +1 -2
  127. data/tests/secure_write_test.rb +6 -1
  128. data/tests/sexy_singleton_test.rb +1 -1
  129. data/tests/string_named_placeholders.rb +109 -0
  130. data/tests/string_version_test.rb +3 -1
  131. data/tests/subhash_test.rb +0 -1
  132. data/tests/test_helper.rb +4 -7
  133. data/tests/time_dummy_test.rb +0 -1
  134. data/tests/time_freezer_test.rb +1 -1
  135. data/tests/to_test.rb +6 -6
  136. data/tests/token_test.rb +0 -1
  137. data/tests/unit_test.rb +0 -1
  138. data/tins.gemspec +18 -30
  139. metadata +55 -38
  140. data/lib/tins/count_by.rb +0 -8
  141. data/lib/tins/deep_const_get.rb +0 -50
  142. data/lib/tins/timed_cache.rb +0 -51
  143. data/lib/tins/uniq_by.rb +0 -10
  144. data/lib/tins/xt/count_by.rb +0 -11
  145. data/lib/tins/xt/deep_const_get.rb +0 -7
  146. data/lib/tins/xt/uniq_by.rb +0 -15
  147. data/tests/count_by_test.rb +0 -17
  148. data/tests/deep_const_get_test.rb +0 -37
  149. data/tests/uniq_by_test.rb +0 -31
  150. /data/{COPYING → LICENSE} +0 -0
data/lib/tins/null.rb CHANGED
@@ -1,68 +1,131 @@
1
1
  module Tins
2
- # Implementation of the null object pattern in Ruby.
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
- def to_str
17
- nil
18
- end
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
- def to_int
29
- nil
30
- end
31
-
66
+ # Convert to array.
67
+ #
68
+ # @return [Array] Empty array
32
69
  def to_a
33
70
  []
34
71
  end
35
72
 
36
- def to_ary
37
- nil
38
- end
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
- module_function
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
- # Raise a runtime error with the inspected objects +objs+ (obtained by
8
- # calling the #inspect method) as their message text. This is useful for
9
- # quick debugging.
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
- # Raise a runtime error with the inspected objects +objs+ (obtained by
15
- # calling the #pretty_inspect method) as their message text. This is useful
16
- # for quick debugging.
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
- # If this module is included into a Proc (or similar object), it tampers
4
- # with its Proc#arity method.
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__) or old_arity.bind(self).call
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
- # Create a partial application of this Proc (or similar object) using
16
- # _args_ as the already applied arguments.
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'
@@ -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
- self.class.new do |*args|
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
@@ -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.call(*list) }
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.call(x, y.__send__(my_method, *args)) }
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
- memoize_function :array, freeze: true
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
- memoize_function :first, freeze: true
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
- memoize_function :second, freeze: true
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
- memoize_function :tail, freeze: true
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
- memoize_function :last, freeze: true
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
- memoize_function :id1, freeze: true
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.call
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.call, block.binding
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