/
columns.jl
298 lines (214 loc) · 6.26 KB
/
columns.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
struct Name{name} end
"""
name_str(name)
Create a typed [`Name`](@ref).
```jldoctest
julia> using LightQuery
julia> name"a"
name"a"
```
"""
macro name_str(name)
esc(Name{Symbol(name)}())
end
export @name_str
"""
Name(name)
Create a typed `Name`. Inverse of [`unname`](@ref). See also [`@name_str`](@ref).
```jldoctest name
julia> using LightQuery
julia> Name(:a)
name"a"
```
`Name`s can be used as selector functions.
```jldoctest name
julia> using Test: @inferred
julia> data = (a = 1, b = 1.0, c = 1, d = 1.0, e = 1, f = 1.0)
(a = 1, b = 1.0, c = 1, d = 1.0, e = 1, f = 1.0)
julia> @inferred name"c"(data)
1
```
Multiple names can be used as selector functions
```jldoctest name
julia> @inferred (name"c", name"f")(data)
(c = 1, f = 1.0)
```
A final use for names can be as a way to construct NamedTuples from pairs. Using `Name`s
instead of `Symbol`s gives type stability.
```jldoctest name
julia> @inferred NamedTuple((name"a", 1), (name"b", 2))
(a = 1, b = 2)
```
"""
@pure function Name(name)
Name{name}()
end
export Name
"""
unname(::Name{name}) where name
Inverse of [`Name`](@ref).
```jldoctest
julia> using LightQuery
julia> using Test: @inferred
julia> @inferred unname(Name(:a))
:a
```
"""
function unname(::Name{name}) where {name}
name
end
export unname
function show(output::IO, ::Name{name}) where {name}
print(output, "name\"", name, '"')
end
const MyNamedTuple = SomeOf{Tuple{Name,Any}}
function recursive_get(initial, name)
throw(BoundsError(initial, (name,)))
end
function recursive_get(initial, name, (check_name, value), rest...)
if name === check_name
value
else
recursive_get(initial, name, rest...)
end
end
function (::Name{name})(object) where {name}
getproperty(object, name)
end
function (name::Name)(data::MyNamedTuple)
# TODO: avoid recursion for large tuples
recursive_get(data, name, data...)
end
function get_pair(data, name)
name, name(data)
end
function (some_names::SomeOf{Name})(data::MyNamedTuple)
partial_map(get_pair, data, some_names)
end
# TODO: a version which works on all types?
function (some_names::SomeOf{Name})(data::NamedTuple)
NamedTuple(some_names(named_tuple(data))...)
end
function isless(::Name{name1}, ::Name{name2}) where {name1,name2}
isless(name1, name2)
end
# to override recusion limit on constant propagation
@pure function to_Names(some_names::SomeOf{Symbol})
my_map(Name, some_names)
end
function to_Names(them)
my_map(Name, Tuple(them))
end
function NamedTuple(data::Tuple{Name,Any}...)
NamedTuple{my_map((function ((key, value),)
unname(key)
end), data)}(my_map(value, data))
end
function named_tuple(data)
partial_map(get_pair, data, to_Names(propertynames(data)))
end
# arghhh im a pirate
@pure function propertynames(data::NamedTuple{names, <:Any}) where {names}
names
end
export MyNamedTuple
"""
remove(data, old_names...)
Remove `old_names` from `data`.
```jldoctest
julia> using LightQuery
julia> using Test: @inferred
julia> data = (a = 1, b = 1.0, c = 1, d = 1.0, e = 1, f = 1.0);
julia> @inferred remove(data, name"c", name"f")
(a = 1, b = 1.0, d = 1.0, e = 1)
```
"""
function remove(data::MyNamedTuple, old_names...)
my_setdiff(my_map(key, data), old_names)(data)
end
function remove(data, old_names...)
NamedTuple(remove(named_tuple(data), old_names...)...)
end
export remove
"""
transform(data, assignments...)
Merge `assignments` into `data`, overwriting old values.
```jldoctest
julia> using LightQuery
julia> using Test: @inferred
julia> data = (a = 1, b = 1.0, c = 1, d = 1.0, e = 1, f = 1.0);
julia> @inferred transform(data, c = 2.0, f = 2, g = 1, h = 1.0)
(a = 1, b = 1.0, d = 1.0, e = 1, c = 2.0, f = 2, g = 1, h = 1.0)
```
"""
function transform(data::MyNamedTuple, assignments...)
remove(data, my_map(key, assignments)...)..., assignments...
end
function transform(data; assignments...)
NamedTuple(transform(named_tuple(data), named_tuple(assignments.data)...)...)
end
export transform
maybe_named_tuple(something) = something
maybe_named_tuple(something::MyNamedTuple) = NamedTuple(something...)
function rename_one(data, (new_name, old_name))
new_name, maybe_named_tuple(old_name(data))
end
"""
rename(data, new_name_old_names...)
Rename `data`.
```jldoctest
julia> using LightQuery
julia> using Test: @inferred
julia> data = (a = 1, b = 1.0, c = 1, d = 1.0, e = 1, f = 1.0);
julia> @inferred rename(data, c2 = name"c", f2 = name"f")
(a = 1, b = 1.0, d = 1.0, e = 1, c2 = 1, f2 = 1.0)
```
"""
function rename(data::MyNamedTuple, new_name_old_names...)
remove(data, my_map(value, new_name_old_names)...)...,
partial_map(rename_one, data, new_name_old_names)...
end
function rename(data; new_name_old_names...)
NamedTuple(rename(named_tuple(data), named_tuple(new_name_old_names.data)...)...)
end
export rename
"""
gather(data, new_name_old_names...)
For each `new_name, old_names` pair in `new_name_old_names`, gather the `old_names` into a single `new_name`. Inverse of [`spread`](@ref).
```jldoctest
julia> using LightQuery
julia> using Test: @inferred
julia> data = (a = 1, b = 1.0, c = 1, d = 1.0, e = 1, f = 1.0);
julia> @inferred gather(data, g = (name"b", name"e"), h = (name"c", name"f"))
(a = 1, d = 1.0, g = (b = 1.0, e = 1), h = (c = 1, f = 1.0))
```
"""
function gather(data::MyNamedTuple, new_name_old_names...)
remove(data, my_flatten(my_map(value, new_name_old_names))...)...,
partial_map(rename_one, data, new_name_old_names)...
end
function gather(data; new_name_old_names...)
NamedTuple(gather(named_tuple(data), named_tuple(new_name_old_names.data)...)...)
end
export gather
"""
spread(data, some_names...)
Unnest nested named tuples. Inverse of [`gather`](@ref).
```jldoctest
julia> using LightQuery
julia> using Test: @inferred
julia> gathered = (a = 1, d = 1.0, g = (b = 1.0, e = 1), h = (c = 1, f = 1.0));
julia> @inferred spread(gathered, name"g", name"h")
(a = 1, d = 1.0, b = 1.0, e = 1, c = 1, f = 1.0)
```
"""
function spread(data::MyNamedTuple, some_names...)
remove(data, some_names...)...,
my_flatten(my_map((function ((name, value),)
named_tuple(value)
end), some_names(data)))...
end
function spread(data, some_names...)
NamedTuple(spread(named_tuple(data), some_names...)...)
end
export spread