lab42_curry 0.1.0 → 0.1.1

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: 9a551847324f71962eb05cec99769fc9a945b68d31b65a3ce94bd338e4f88b72
4
- data.tar.gz: 6bb0044d5af9008de8ff78d00f4b5a7e1d79c9c341f2e78f1f0012a909670160
3
+ metadata.gz: 364ab07ebc3ae04d88f52078f9685de77f1ac5f7acc22c0fdc14352c80e14c93
4
+ data.tar.gz: fe68d0fa92be55bae32d4eee096ef0989a94381701e9a41a31b31c516977145b
5
5
  SHA512:
6
- metadata.gz: f0028228fd18f8cc4ec1b4a865ad64e3b57ea99a2ded385cfbea0527fb61744c03d2bf35163ea4a5698bdc85c67e9e469b7ea213c7a25781f9b9e365387a02a1
7
- data.tar.gz: f9ed56c17f25fe649f2507cae0cc2a55f4a6170b7901b3096235742f155aa34e80f592ab219f1d2a9a1665239e62e71e7a0d5db80e27d1f880b563d9e9a92f0e
6
+ metadata.gz: e98215ae8b0a6ea139afe52e4f98648e351f48c950f336231002b134837b15254687649357205c6942e9659a9c3966a0464b875cc6dce949b4410857fe7979ba
7
+ data.tar.gz: 27948a35f86d517a2eb681a2d378b0d13fee608990d2f958eff1fc206551b3cfdc69d01ab09b4c2630eaf6761d9298a13bfd020eb703a1135b49be05d422c282
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
- [![Gem Version](https://badge.fury.io/rb/lab42_curry.svg)](http://badge.fury.io/rb/lab42_curry)
2
-
1
+ [![Gem Version](http://img.shields.io/gem/v/lab42_curry.svg)](https://rubygems.org/gems/lab42_curry)
2
+ [![CI](https://github.com/robertdober/lab42_curry/workflows/CI/badge.svg)](https://github.com/robertdober/lab42_curry/actions)
3
+ [![Coverage Status](https://coveralls.io/repos/github/RobertDober/lab42_curry/badge.svg?branch=master)](https://coveralls.io/github/RobertDober/lab42_curry?branch=master)
3
4
 
4
5
  # Lab42::Curry
5
6
 
@@ -20,8 +21,9 @@ Given such a simple funcion
20
21
  ```ruby
21
22
  def adder(a, b, c); a + 10*b + 100*c end
22
23
  let(:add_to_1) {curry(:adder, 1)}
24
+ # Equivalent to Elixir's &adder(1, &1, &2)
23
25
  ```
24
- **N.B.** that `Lab42::Curry` has been included into Examples **and** ExampleGroups in `spec/spec_helper.rb`
26
+ **N.B.** that `Lab42::Curry` has been included into Examples **and** ExampleGroups in `spec/spec_helper.rb`
25
27
 
26
28
  Then very unsurprisingly:
27
29
  ```ruby
@@ -31,7 +33,7 @@ Then very unsurprisingly:
31
33
  We call the arguments passed into `curry` the _compiletime arguments_, and the arguments passed into the
32
34
  invocation of the _curried function_, which has been returned by the invocation of `curry`, the _runtime arguments_.
33
35
 
34
- In our case the _compiletime arguments_ were `[1]` and the _runtime arguments_ were `[2, 3]`
36
+ In our case the _compiletime arguments_ were `[1]` and the _runtime arguments_ were `[2, 3]`
35
37
 
36
38
  ### Reordering
37
39
 
@@ -39,7 +41,8 @@ There are several methods of reordering arguments, the simplest is probably usin
39
41
 
40
42
  When a placeholder is provided (`Lab42::Curry.runtime_arg` aliased as `rt_arg` )
41
43
  ```ruby
42
- let(:add_to_30) { curry(:adder, rt_arg, 3) }
44
+ let(:add_to_30) { curry(:adder, rt_arg, 3) }
45
+ # Equivalent to Elixir's &adder(&1, 3, &2)
43
46
  ```
44
47
  Then we see that
45
48
  ```ruby
@@ -47,30 +50,33 @@ Then we see that
47
50
  ```
48
51
  #### Total control over argument order...
49
52
 
50
- ... can be achieved by passing the index of the positional argument to be used into `Lab42::Curry.runtime_arg`
53
+ ... can be achieved by passing the index of the positional argument to be used into `Lab42::Curry.runtime_arg`
51
54
 
52
55
  Given the total reorder form
53
56
  ```ruby
54
- let(:mul_decrement) { curry(:adder, runtime_arg(2), runtime_arg(0), 1) } # now first argument is c (index 2) and second a (index 0) and b = 1
57
+ let(:twohundred_three) { curry(:adder, runtime_arg(2), runtime_arg(0), 1) }
58
+ # now first argument is c (index 2) and second a (index 0) and b = 1
59
+ # Like Elixir's &adder(&2, 1, &1)
55
60
  ```
56
61
  Then we have
57
62
  ```ruby
58
- expect( mul_decrement.(2, 3) ).to eq(213)
63
+ expect( twohundred_three.(2, 3) ).to eq(213)
59
64
  ```
60
65
 
61
- #### Picking a positon for a compiletime argument
66
+ #### Picking a position for a compiletime argument
62
67
 
63
- It might be cumbersome to write things like: `curry(..., rt_arg, rt_arg, ..., rt_arg, 42)`
68
+ It might be cumbersome to write things like: `curry(..., rt_arg, rt_arg, ..., rt_arg, 42)`
64
69
 
65
- Therefore we can express the same much more concisely with `Lab42::Curry.compiletime_args`, and its alias `ct_args`
70
+ Therefore we can express the same much more concisely with `Lab42::Curry.compiletime_args`, and its alias `ct_args`
66
71
 
67
72
  Given
68
73
  ```ruby
69
- let(:doubler) { curry(:adder, ct_args(2 => 2)) } # same as curry(:adder, rt_arg, rt_arg, 2)
74
+ let(:twohundred) { curry(:adder, ct_args(2 => 2)) }
75
+ # same as curry(:adder, rt_arg, rt_arg, 2)
70
76
  ```
71
77
  Then we get
72
78
  ```ruby
73
- expect( doubler.(4, 3) ).to eq(234)
79
+ expect( twohundred.(4, 3) ).to eq(234)
74
80
  ```
75
81
 
76
82
  **N.B.** that we could have defined `add_to_30` as `curry(:adder, rt_arg, 3, rt_arg)` of course
@@ -78,10 +84,10 @@ Then we get
78
84
 
79
85
  #### Error Handling
80
86
 
81
- When you indicate values for the same position multiple times
87
+ When you indicate values for the same position multiple times
82
88
  Then the `ArgumentCompiler` saves you:
83
89
  ```ruby
84
- expect{ curry(:adder, 1, ct_args(0 => 1)).() }.to raise_error(Lab42::Curry::DuplicatePositionSpecification)
90
+ expect{ curry(:adder, 1, ct_args(0 => 1)) }.to raise_error(Lab42::Curry::DuplicatePositionSpecification)
85
91
  ```
86
92
 
87
93
  ### Context With proc like objects
@@ -95,6 +101,41 @@ Then we will get the negative value
95
101
  ```ruby
96
102
  expect( inverse.(2, 1) ).to eq(-1)
97
103
  ```
104
+
105
+ ### Context Keyword Arguments
106
+
107
+ Given a function which takes keyword arguments like the following
108
+ ```ruby
109
+ def rectangle(length, width, border: 0, color: )
110
+ [length, width, border, color]
111
+ end
112
+ let(:red_rectangle) { curry(:rectangle, color: :red) }
113
+ let(:wide_bordered) { curry(:rectangle, rt_arg, 999, border: 1) }
114
+ ```
115
+
116
+ Then the red rectangle gives us
117
+ ```ruby
118
+ expect( red_rectangle.(1, 2) ).to eq([1, 2, 0, :red])
119
+ expect( red_rectangle.(1, 2, border: 1) ).to eq([1, 2, 1, :red])
120
+ ```
121
+
122
+ Can we override curried values, normally not
123
+ Example: cannot override
124
+ ```ruby
125
+ expect{ red_rectangle.(1, 2, color: :blue) }
126
+ .to raise_error(
127
+ Lab42::Curry::DuplicateKeywordArgument,
128
+ "keyword argument :color is already defined with value :red cannot override with :blue")
129
+ ```
130
+
131
+ But we can create a more lenient curry with `curry!`
132
+ ```ruby
133
+ expect( curry!(:rectangle, 1, 2, color: :red ).(color: :blue) )
134
+ .to eq([1, 2, 0, :blue])
135
+ ```
136
+
137
+
138
+
98
139
  # LICENSE
99
140
 
100
141
  Copyright 2020,1 Robert Dober robert.dober@gmail.com
@@ -4,8 +4,14 @@ require_relative "curry/compiletime_args"
4
4
 
5
5
  module Lab42
6
6
  module Curry
7
- def curry(method_or_name, *curry_time_args, &blk)
8
- curry_data = Data.new(self, method_or_name, *curry_time_args, &blk)
7
+ def curry(method_or_name, *curry_time_args, **curry_time_kwds, &blk)
8
+ curry_data = Data.new(self, method_or_name, *curry_time_args, **curry_time_kwds, &blk)
9
+ curry_data.curried
10
+ end
11
+
12
+ def curry!(method_or_name, *curry_time_args, **curry_time_kwds, &blk)
13
+ curry_data = Data.new(self, method_or_name, *curry_time_args, **curry_time_kwds, &blk)
14
+ curry_data.allow_kwd_override = true
9
15
  curry_data.curried
10
16
  end
11
17
 
@@ -5,74 +5,74 @@ require_relative 'errors'
5
5
  module Lab42
6
6
  module Curry
7
7
  class ArgCompiler
8
- attr_reader :ct_args, :rt_args
9
-
10
- def compile_args
11
- _init
12
- ct_args.each(&method(:_set_ct_arg))
13
- rt_args.each(&method(:_set_rt_arg))
14
- @final.map.sort_by(&:first).map(&:last)
8
+
9
+ def compile_args rt_args
10
+ @__compiled_args__ ||= _compile_args(rt_args)
15
11
  end
16
-
12
+
17
13
  private
18
- def initialize(ct_args:, rt_args:)
19
- @ct_args = ct_args
14
+ def initialize(ct_args)
15
+ _init
16
+ _determine_predefined_positions! ct_args
17
+ end
18
+
19
+ def _compile_args rt_args
20
20
  @rt_args = rt_args
21
+ @rt_arg_positions.each(&method(:_set_rt_arg))
22
+ @rt_args.each(&method(:_set_final!))
23
+ @final.map.sort_by(&:first).map(&:last)
24
+ end
25
+
26
+ def _determine_predefined_positions! ct_args
27
+ ct_args.each(&method(:_set_ct_arg))
21
28
  end
22
29
 
23
30
  def _init
31
+ @rt_arg_positions = []
32
+ # invariant @pos is lowest free index in @final
33
+ # update both together with _set_final!( value, idx = @pos )
24
34
  @pos = 0
25
35
  @final = {}
26
36
  end
27
37
 
28
- def _set_compiletime_arg ct_arg
29
- _store_into_next(ct_arg)
30
- end
31
-
32
38
  def _set_ct_arg ct_arg
33
39
  case ct_arg
34
40
  when RuntimeArg
35
- _set_runtime_arg! ct_arg # changes @rt_args, I know, I know, TODO: make this w/o side effect
41
+ _set_target_position! ct_arg.position
36
42
  when CompiletimeArgs
37
- _set_positions ct_arg
43
+ _set_positions! ct_arg
38
44
  else
39
- _set_compiletime_arg ct_arg
45
+ _set_final! ct_arg
40
46
  end
41
47
  end
42
48
 
43
- def _set_position((pos, val))
44
- _store_at! val, pos
49
+ def _set_final! value, pos=nil
50
+ pos ||= @pos
51
+ raise DuplicatePositionSpecification, "There is already a curried value for #{pos}" if @final.has_key?(pos)
52
+ @final[pos] = value
53
+ while @final.has_key?(@pos)
54
+ @pos += 1
55
+ end
45
56
  end
46
57
 
47
- def _set_positions ct_arg
48
- ct_arg.each(&method(:_set_position))
58
+ def _set_position!((position, value))
59
+ _set_final! value, position
49
60
  end
50
61
 
51
- def _set_rt_arg rt_arg
52
- _store_into_next(rt_arg)
62
+ def _set_positions! ct_arg
63
+ ct_arg.each(&method(:_set_position!))
53
64
  end
54
65
 
55
- def _set_runtime_arg! ct_arg
56
- if ct_arg.position
57
- _store_at! rt_args.shift, ct_arg.position # This is the culprit
58
- else
59
- _store_into_next(rt_args.shift) # Oops I did it again
60
- end
66
+ def _set_rt_arg rt_arg_position
67
+ raise MissingRuntimeArg, "for position #{rt_arg_position}" if @rt_args.empty?
68
+ @final[rt_arg_position] = @rt_args.shift
61
69
  end
62
70
 
63
- def _store_at!(value, pos=nil)
64
- pos ||= @pos
65
- raise DuplicatePositionSpecification, "There is already a curried value #{@final[pos].inspect} at position #{pos}, attempting to override with #{value.inspect}" if
66
- @final.has_key?(pos)
67
- @final[pos] = value
71
+ def _set_target_position! position
72
+ @rt_arg_positions << (position || @pos)
73
+ _set_final! RuntimeArg, position
68
74
  end
69
75
 
70
- def _store_into_next(value)
71
- while @final.has_key?(@pos)
72
- @pos += 1
73
- end
74
- _store_at! value
75
- end
76
76
  end
77
77
  end
78
78
  end
@@ -3,12 +3,15 @@ require_relative 'arg_compiler'
3
3
  module Lab42
4
4
  module Curry
5
5
  class Data
6
- attr_reader :context, :ct_args, :curried
6
+ attr_accessor :allow_kwd_override
7
+ attr_reader :context, :ct_args, :ct_kwds, :curried
7
8
 
8
9
  private
9
- def initialize(ctxt, method_or_name, *curry_time_args, &blk)
10
+ def initialize(ctxt, method_or_name, *curry_time_args, **curry_time_kwds, &blk)
11
+ @allow_kwd_override
10
12
  @context = ctxt
11
13
  @ct_args = curry_time_args
14
+ @ct_kwds = curry_time_kwds
12
15
 
13
16
  @mthd =
14
17
  case method_or_name
@@ -17,16 +20,24 @@ module Lab42
17
20
  else
18
21
  method_or_name
19
22
  end
20
- _curry(&blk)
21
- end
22
23
 
23
- def _compile_final_args(rt_args)
24
- arg_compiler = ArgCompiler.new(ct_args: ct_args, rt_args: rt_args)
25
- arg_compiler.compile_args
24
+ @arg_compiler = ArgCompiler.new(ct_args)
25
+ _curry(&blk)
26
26
  end
27
27
 
28
28
  def _curry(&blk)
29
- @curried = ->(*rt_args) { @mthd.(*_compile_final_args(rt_args)) }
29
+ @curried = ->(*rt_args, **rt_kwds) do
30
+ @mthd.(
31
+ *@arg_compiler.compile_args(rt_args),
32
+ **@ct_kwds.merge(rt_kwds) { |kwd, old_val, new_val|
33
+ if allow_kwd_override
34
+ new_val
35
+ else
36
+ raise DuplicateKeywordArgument, "keyword argument #{kwd.inspect} is already defined with value #{old_val.inspect} cannot override with #{new_val.inspect}"
37
+ end
38
+ }
39
+ )
40
+ end
30
41
  end
31
42
  end
32
43
  end
@@ -1,5 +1,7 @@
1
1
  module Lab42
2
2
  module Curry
3
3
  DuplicatePositionSpecification = Class.new RuntimeError
4
+ DuplicateKeywordArgument = Class.new RuntimeError
5
+ MissingRuntimeArg = Class.new RuntimeError
4
6
  end
5
7
  end
@@ -1,5 +1,5 @@
1
1
  module Lab42
2
2
  module Curry
3
- VERSION = "0.1.0"
3
+ VERSION = "0.1.1"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lab42_curry
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Dober
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-26 00:00:00.000000000 Z
11
+ date: 2020-12-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry
@@ -80,7 +80,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
80
80
  requirements:
81
81
  - - ">="
82
82
  - !ruby/object:Gem::Version
83
- version: 3.0.0
83
+ version: 2.7.0
84
84
  required_rubygems_version: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - ">="