lab42_curry 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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
  - - ">="