-
Notifications
You must be signed in to change notification settings - Fork 0
/
core.jl
187 lines (161 loc) · 5.17 KB
/
core.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
"Abstract type for Thunks."
abstract type Think end
abstract type WrappedThink <: Think end
"""
Thunk(function, args, kwargs)
Type that represents a thunk. Only `evaluated` is a public field.
Use `reify` to evaluate. Evaluates to Weak head normal form, meaning that
non-free symbols must be assigned / defined. Contrast with Unevaluated.
"""
mutable struct Thunk <: Think
f::Any # usually a Function, but could be any callable
# args will be passed to f. Needs to support ... (splat), eg Array or Tuple
args::Any
kwargs::Dict # kwargs will be passed to f, cleared after evaluation
evaluated::Bool # false until computed, then true
result::Any # cache result once computed
Thunk(f, args) = new(f, args, Dict(), false, nothing)
Thunk(f, args, kwargs) = new(f, args, kwargs, false, nothing)
end
"""
Keep expression, including args/kwargs, as unevaluated.
"""
mutable struct Unevaluated <: Think
f::Any # usually a Function, but could be any callable
# args will be passed to f. Needs to support ... (splat), eg Array or Tuple
args::Any # a primitive thunk of the form: () -> tuple
kwargs::Any # a primitive thunk of the form: () -> dict
evaluated::Bool # false until computed, then true
result::Any # cache result once computed
Unevaluated(f, args) = new(f, args, ()->Dict(), false, nothing)
Unevaluated(f, args, kwargs) = new(f, args, kwargs, false, nothing)
end
"""
A Thunk that can "undo" code evaluation. Does not clear f or args.
Useful for interactive coding. ie if you revise a function definition,
can undo & reify the thunk again.
"""
mutable struct Reversible <: Think
f::Any # usually a Function, but could be any callable
# args will be passed to f. Needs to support ... (splat), eg Array or Tuple
args::Any
kwargs::Dict # kwargs will be passed to f, cleared after evaluation
evaluated::Bool # false until computed, then true
result::Any # cache result once computed
Reversible(f, args) = new(f, args, Dict(), false, nothing)
Reversible(f, args, kwargs) = new(f, args, kwargs, false, nothing)
end
"No operation."
function noop(args...; kwargs...)
nothing
end
"""
A Thunk that can be checkpointed.
"""
mutable struct Checkpointable <: WrappedThink
wrapped_thunk::Think
# function that takes value of wrapped_thunk, and stores the result, ie on disk
checkpoint::Any
# instead of evaluating wrapped_thunk, try restoring value from disk
restore::Any # load the result. ideally, no dependencies
Checkpointable(t,c,r) = new(t,c,r)
Checkpointable(t,r) = new(t,noop,r)
end
function Base.getindex(self::Think, index)
thunk(getindex)(self, index)
end
Base.iterate(t::Think, state=1) = (t[state], state+1)
function thunk(f)
(args...; kwargs...) -> Thunk(f, args, kwargs)
end
"""
reify(thunk::Think)
reify(value::Any)
Reify a thunk into a value.
In other words, compute the value of the expression.
We walk through the thunk's arguments and keywords, recursively evaluating each one,
and then evaluating the thunk's function with the evaluated arguments.
"""
function set_result(thunk::WrappedThink)
set_result(thunk.wrapped_thunk)
end
function set_result(thunk::Think, result)
thunk.result = result
thunk.evaluated = true
# clear to allow garbage collection
thunk.args = []
thunk.kwargs = Dict()
end
function set_result(thunk::Reversible, result)
thunk.result = result
thunk.evaluated = true
end
function reify(thunk::Think)
if thunk.evaluated
return thunk.result
else
args = [reify(x) for x in thunk.args]
kwargs = Dict(k => reify(v) for (k,v) in thunk.kwargs)
result = thunk.f(args...; kwargs...)
set_result(thunk, result)
return result
end
end
function reify(thunk::Unevaluated)
if thunk.evaluated
return thunk.result
else
args = [reify(x) for x in thunk.args()]
kwargs = Dict(k => reify(v) for (k,v) in thunk.kwargs())
result = thunk.f(args...; kwargs...)
set_result(thunk, result)
return result
end
end
function reify(thunk::Reversible)
if thunk.evaluated
return thunk.result
else
args = [reify(x) for x in thunk.args]
kwargs = Dict(k => reify(v) for (k,v) in thunk.kwargs)
result = thunk.f(args...; kwargs...)
set_result(thunk, result)
return result
end
end
function reify(thunk::Checkpointable)
if thunk.wrapped_thunk.evaluated
return thunk.wrapped_thunk.result
end
result = nothing
try
result = reify(thunk.restore)
catch
for (exc, bt) in Base.catch_stack()
showerror(stdout, exc, bt)
println(stdout)
end
end
if ~isnothing(result)
set_result(thunk.wrapped_thunk, result)
return result
else
result = reify(thunk.wrapped_thunk)
reify(thunk.checkpoint)(result)
return result
end
end
function reify(value)
value
end
"Undo function call (non-recursively)"
function undo(thunk::Reversible)
thunk.result = nothing
thunk.evaluated = false
thunk
end
"Undo function call (non-recursively)"
function undo(thunk::WrappedThink)
undo(thunk.wrapped_thunk)
thunk
end