Skip to content

Commit 47b0a97

Browse files
committed
Implement lifting infrastructure
1 parent 35b2583 commit 47b0a97

File tree

3 files changed

+194
-0
lines changed

3 files changed

+194
-0
lines changed

base/exports.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,6 +1326,7 @@ export
13261326
# nullable types
13271327
isnull,
13281328
unsafe_get,
1329+
Lifted,
13291330

13301331
# Macros
13311332
# parser internal

base/nullable.jl

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,3 +217,127 @@ function hash(x::Nullable, h::UInt)
217217
return hash(x.value, h + nullablehash_seed)
218218
end
219219
end
220+
221+
"""
222+
Lifted{F}
223+
224+
A type used to represent the lifted version of a function `f::F`.
225+
226+
Calling an `_f::Lifted{F}` on arguments `xs...` lowers to
227+
`lift(_f.f, U, xs...)`, where the return type parameter `U` is chosen with the
228+
help of type inference.
229+
"""
230+
immutable Lifted{F}
231+
f::F
232+
cache::Dict{Tuple{Vararg{DataType}}, DataType}
233+
234+
(::Type{Lifted}){F}(f::F) = new{F}(
235+
f, Dict{Tuple{Vararg{DataType}}, DataType}()
236+
)
237+
end
238+
239+
function (_f::Lifted{F}){F}(xs...)
240+
f, cache = _f.f, _f.cache
241+
signature = map(eltype, xs)
242+
U = Base.@get!(
243+
cache,
244+
signature,
245+
Core.Inference.return_type(f, Tuple{signature...})
246+
)
247+
return lift(f, U, xs...)
248+
end
249+
250+
"""
251+
lift(f::F)::Lifted{F}
252+
253+
Return a lifted version of `f`.
254+
"""
255+
lift(f) = Lifted(f)
256+
257+
"""
258+
lift(f, U, xs...)
259+
260+
Return an empty `Nullable{U}` if any of the `xs` is null; otherwise, return the
261+
(`Nullable`-wrapped) value of `f` applied to the values of the `xs`.
262+
263+
NOTE: There are two exceptions to the above: `lift(|, Bool, x, y)` and
264+
`lift(&, Bool, x, y)`. These methods both follow three-valued logic semantics.
265+
"""
266+
function lift(f, U::DataType, x)
267+
if isnull(x)
268+
return Nullable{U}()
269+
else
270+
return Nullable{U}(f(unsafe_get(x)))
271+
end
272+
end
273+
274+
function lift(f, U::DataType, x1, x2)
275+
if isnull(x1) | isnull(x2)
276+
return Nullable{U}()
277+
else
278+
return Nullable{U}(f(unsafe_get(x1), unsafe_get(x2)))
279+
end
280+
end
281+
282+
function lift(f, U::DataType, xs...)
283+
if mapreduce(isnull, |, false, xs)
284+
return Nullable{U}()
285+
else
286+
return Nullable{U}(f(map(unsafe_get, xs)...))
287+
end
288+
end
289+
290+
# Three-valued logic
291+
292+
(::Lifted{&})(x::Union{Bool, Nullable{Bool}}, y::Union{Bool, Nullable{Bool}}) =
293+
lift(&, Bool, x, y)
294+
(::Lifted{|})(x::Union{Bool, Nullable{Bool}}, y::Union{Bool, Nullable{Bool}}) =
295+
lift(|, Bool, x, y)
296+
297+
function lift(f::typeof(&), ::Type{Bool}, x, y)::Nullable{Bool}
298+
return ifelse(
299+
isnull(x),
300+
ifelse(
301+
isnull(y),
302+
Nullable{Bool}(),
303+
ifelse(
304+
unsafe_get(y),
305+
Nullable{Bool}(),
306+
Nullable(false)
307+
)
308+
),
309+
ifelse(
310+
isnull(y),
311+
ifelse(
312+
unsafe_get(x),
313+
Nullable{Bool}(),
314+
Nullable(false)
315+
),
316+
Nullable(unsafe_get(x) & unsafe_get(y))
317+
)
318+
)
319+
end
320+
321+
function lift(f::typeof(|), ::Type{Bool}, x, y)::Nullable{Bool}
322+
return ifelse(
323+
isnull(x),
324+
ifelse(
325+
isnull(y),
326+
Nullable{Bool}(),
327+
ifelse(
328+
unsafe_get(y),
329+
Nullable(true),
330+
Nullable{Bool}()
331+
)
332+
),
333+
ifelse(
334+
isnull(y),
335+
ifelse(
336+
unsafe_get(x),
337+
Nullable(true),
338+
Nullable{Bool}()
339+
),
340+
Nullable(unsafe_get(x) | unsafe_get(y))
341+
)
342+
)
343+
end

test/nullable.jl

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,3 +387,72 @@ end
387387

388388
# issue #11675
389389
@test repr(Nullable()) == "Nullable{Union{}}()"
390+
391+
# lifting
392+
393+
f(x::Number) = 5 * x
394+
f(x::Number, y::Number) = x + y
395+
f(x::Number, y::Number, z::Number) = x + y * z
396+
_f = lift(f)
397+
398+
for T in setdiff(types, [Bool])
399+
a = one(T)
400+
x = Nullable{T}(a)
401+
y = Nullable{T}()
402+
403+
U1 = Core.Inference.return_type(f, Tuple{T})
404+
@test isequal(_f(x), Nullable(f(a)))
405+
@test isequal(_f(y), Nullable{U1}())
406+
407+
U2 = Core.Inference.return_type(f, Tuple{T, T})
408+
@test isequal(_f(x, x), Nullable(f(a, a)))
409+
@test isequal(_f(x, y), Nullable{U2}())
410+
411+
U3 = Core.Inference.return_type(f, Tuple{T, T, T})
412+
@test isequal(_f(x, x, x), Nullable(f(a, a, a)))
413+
@test isequal(_f(x, y, x), Nullable{U3}())
414+
end
415+
416+
# three-valued logic
417+
418+
# & truth table
419+
v1 = lift(&, Bool, Nullable(true), Nullable(true))
420+
v2 = lift(&, Bool, Nullable(true), Nullable(false))
421+
v3 = lift(&, Bool, Nullable(true), Nullable{Bool}())
422+
v4 = lift(&, Bool, Nullable(false), Nullable(true))
423+
v5 = lift(&, Bool, Nullable(false), Nullable(false))
424+
v6 = lift(&, Bool, Nullable(false), Nullable{Bool}())
425+
v7 = lift(&, Bool, Nullable{Bool}(), Nullable(true))
426+
v8 = lift(&, Bool, Nullable{Bool}(), Nullable(false))
427+
v9 = lift(&, Bool, Nullable{Bool}(), Nullable{Bool}())
428+
429+
@test isequal(v1, Nullable(true))
430+
@test isequal(v2, Nullable(false))
431+
@test isequal(v3, Nullable{Bool}())
432+
@test isequal(v4, Nullable(false))
433+
@test isequal(v5, Nullable(false))
434+
@test isequal(v6, Nullable(false))
435+
@test isequal(v7, Nullable{Bool}())
436+
@test isequal(v8, Nullable(false))
437+
@test isequal(v9, Nullable{Bool}())
438+
439+
# | truth table
440+
u1 = lift(|, Bool, Nullable(true), Nullable(true))
441+
u2 = lift(|, Bool, Nullable(true), Nullable(false))
442+
u3 = lift(|, Bool, Nullable(true), Nullable{Bool}())
443+
u4 = lift(|, Bool, Nullable(false), Nullable(true))
444+
u5 = lift(|, Bool, Nullable(false), Nullable(false))
445+
u6 = lift(|, Bool, Nullable(false), Nullable{Bool}())
446+
u7 = lift(|, Bool, Nullable{Bool}(), Nullable(true))
447+
u8 = lift(|, Bool, Nullable{Bool}(), Nullable(false))
448+
u9 = lift(|, Bool, Nullable{Bool}(), Nullable{Bool}())
449+
450+
@test isequal(u1, Nullable(true))
451+
@test isequal(u2, Nullable(true))
452+
@test isequal(u3, Nullable(true))
453+
@test isequal(u4, Nullable(true))
454+
@test isequal(u5, Nullable(false))
455+
@test isequal(u6, Nullable{Bool}())
456+
@test isequal(u7, Nullable(true))
457+
@test isequal(u8, Nullable{Bool}())
458+
@test isequal(u9, Nullable{Bool}())

0 commit comments

Comments
 (0)