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 +4 -4
- data/README.md +56 -15
- data/lib/lab42/curry.rb +8 -2
- data/lib/lab42/curry/arg_compiler.rb +40 -40
- data/lib/lab42/curry/data.rb +19 -8
- data/lib/lab42/curry/errors.rb +2 -0
- data/lib/lab42/curry/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 364ab07ebc3ae04d88f52078f9685de77f1ac5f7acc22c0fdc14352c80e14c93
|
4
|
+
data.tar.gz: fe68d0fa92be55bae32d4eee096ef0989a94381701e9a41a31b31c516977145b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e98215ae8b0a6ea139afe52e4f98648e351f48c950f336231002b134837b15254687649357205c6942e9659a9c3966a0464b875cc6dce949b4410857fe7979ba
|
7
|
+
data.tar.gz: 27948a35f86d517a2eb681a2d378b0d13fee608990d2f958eff1fc206551b3cfdc69d01ab09b4c2630eaf6761d9298a13bfd020eb703a1135b49be05d422c282
|
data/README.md
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
[![Gem Version](
|
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(:
|
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(
|
63
|
+
expect( twohundred_three.(2, 3) ).to eq(213)
|
59
64
|
```
|
60
65
|
|
61
|
-
#### Picking a
|
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(:
|
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(
|
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))
|
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
|
data/lib/lab42/curry.rb
CHANGED
@@ -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
|
-
|
9
|
-
|
10
|
-
|
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
|
19
|
-
|
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
|
-
|
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
|
-
|
45
|
+
_set_final! ct_arg
|
40
46
|
end
|
41
47
|
end
|
42
48
|
|
43
|
-
def
|
44
|
-
|
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
|
48
|
-
|
58
|
+
def _set_position!((position, value))
|
59
|
+
_set_final! value, position
|
49
60
|
end
|
50
61
|
|
51
|
-
def
|
52
|
-
|
62
|
+
def _set_positions! ct_arg
|
63
|
+
ct_arg.each(&method(:_set_position!))
|
53
64
|
end
|
54
65
|
|
55
|
-
def
|
56
|
-
if
|
57
|
-
|
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
|
64
|
-
|
65
|
-
|
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
|
data/lib/lab42/curry/data.rb
CHANGED
@@ -3,12 +3,15 @@ require_relative 'arg_compiler'
|
|
3
3
|
module Lab42
|
4
4
|
module Curry
|
5
5
|
class Data
|
6
|
-
|
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
|
-
|
24
|
-
|
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
|
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
|
data/lib/lab42/curry/errors.rb
CHANGED
data/lib/lab42/curry/version.rb
CHANGED
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.
|
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-
|
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:
|
83
|
+
version: 2.7.0
|
84
84
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
85
|
requirements:
|
86
86
|
- - ">="
|