-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
proc.cr
223 lines (210 loc) · 5.79 KB
/
proc.cr
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
# A `Proc` represents a function pointer with an optional context (the closure data).
# It is typically created with a proc literal:
#
# ```
# # A proc without arguments
# ->{ 1 } # Proc(Int32)
#
# # A proc with one argument
# ->(x : Int32) { x.to_s } # Proc(Int32, String)
#
# # A proc with two arguments:
# ->(x : Int32, y : Int32) { x + y } # Proc(Int32, Int32, Int32)
# ```
#
# See [`Proc` literals](https://crystal-lang.org/reference/syntax_and_semantics/literals/proc.html) in the language reference.
#
# The types of the arguments (`T`) are mandatory, except when directly
# sending a proc literal to a lib fun in C bindings.
#
# The return type (`R`) is inferred from the proc's body.
#
# A special new method is provided too:
#
# ```
# Proc(Int32, String).new { |x| x.to_s } # Proc(Int32, String)
# ```
#
# This form allows you to specify the return type and to check it
# against the proc's body.
#
# Another way to create a `Proc` is by capturing a block:
#
# ```
# def capture(&block : Int32 -> Int32)
# # block argument is used, so block is turned into a Proc
# block
# end
#
# proc = capture { |x| x + 1 } # Proc(Int32, Int32)
# proc.call(1) # => 2
# ```
#
# When capturing blocks, the type of the arguments and return type
# must be specified in the capturing method block signature.
#
# ### Passing a Proc to a C function
#
# Passing a `Proc` to a C function, for example as a callback, is possible
# as long as the `Proc` isn't a closure. If it is, either a compile-time or
# runtime error will happen depending on whether the compiler can check this.
# The reason is that a `Proc` is internally represented as two void pointers,
# one having the function pointer and another the closure data. If just
# the function pointer is passed, the closure data will be missing at invocation time.
#
# Most of the time a C function that allows setting a callback also provide
# an argument for custom data. This custom data is then sent as an argument
# to the callback. For example, suppose a C function that invokes a callback
# at every tick, passing that tick:
#
# ```
# lib LibTicker
# fun on_tick(callback : (Int32, Void* ->), data : Void*)
# end
# ```
#
# To properly define a wrapper for this function we must send the `Proc` as the
# callback data, and then convert that callback data to the `Proc` and finally invoke it.
#
# ```
# module Ticker
# # The callback for the user doesn't have a Void*
# @@box = Pointer(Void).null
#
# def self.on_tick(&callback : Int32 ->)
# # Since Proc is a {Void*, Void*}, we can't turn that into a Void*, so we
# # "box" it: we allocate memory and store the Proc there
# boxed_data = Box.box(callback)
#
# # We must save this in Crystal-land so the GC doesn't collect it (*)
# @@box = boxed_data
#
# # We pass a callback that doesn't form a closure, and pass the boxed_data as
# # the callback data
# LibTicker.on_tick(->(tick, data) {
# # Now we turn data back into the Proc, using Box.unbox
# data_as_callback = Box(typeof(callback)).unbox(data)
# # And finally invoke the user's callback
# data_as_callback.call(tick)
# }, boxed_data)
# end
# end
#
# Ticker.on_tick do |tick|
# puts tick
# end
# ```
#
# Note that we save the box in `@@box`. The reason is that if we don't do it,
# and our code doesn't reference it anymore, the GC will collect it.
# The C library will of course store the callback, but Crystal's GC has
# no way of knowing that.
struct Proc
def self.new(pointer : Void*, closure_data : Void*)
func = {pointer, closure_data}
ptr = pointerof(func).as(self*)
ptr.value
end
# Creates a `Proc` by capturing the given *block*.
#
# The block argument types are inferred from the `Proc`'s type arguments. The
# return type of the block must match the return type specified in the `Proc`
# type.
#
# ```
# gt = Proc(Int32, Int32, Bool).new do |x, y|
# x > y
# end
# gt.call(3, 1) # => true
# gt.call(1, 2) # => false
# ```
def self.new(&block : self)
block
end
# Returns a new `Proc` that has its first arguments fixed to
# the values given by *args*.
#
# See [Wikipedia, Partial application](https://en.wikipedia.org/wiki/Partial_application)
#
# ```
# add = ->(x : Int32, y : Int32) { x + y }
# add.call(1, 2) # => 3
#
# add_one = add.partial(1)
# add_one.call(2) # => 3
# add_one.call(10) # => 11
#
# add_one_and_two = add_one.partial(2)
# add_one_and_two.call # => 3
# ```
def partial(*args : *U) forall U
{% begin %}
{% remaining = (T.size - U.size) %}
->(
{% for i in 0...remaining %}
arg{{i}} : {{T[i + U.size]}},
{% end %}
) {
call(
*args,
{% for i in 0...remaining %}
arg{{i}},
{% end %}
)
}
{% end %}
end
# Returns the number of arguments of this `Proc`.
#
# ```
# add = ->(x : Int32, y : Int32) { x + y }
# add.arity # => 2
#
# neg = ->(x : Int32) { -x }
# neg.arity # => 1
# ```
def arity
{{T.size}}
end
def pointer : Void*
internal_representation[0]
end
def closure_data : Void*
internal_representation[1]
end
def closure? : Bool
!closure_data.null?
end
private def internal_representation
func = self
ptr = pointerof(func).as({Void*, Void*}*)
ptr.value
end
def ==(other : self)
pointer == other.pointer && closure_data == other.closure_data
end
def ===(other : self)
self == other
end
def ===(other)
call(other)
end
# See `Object#hash(hasher)`
def hash(hasher)
internal_representation.hash(hasher)
end
def clone
self
end
def to_s(io : IO) : Nil
io << "#<"
io << {{@type.name.stringify}}
io << ":0x"
pointer.address.to_s(io, 16)
if closure?
io << ":closure"
end
io << '>'
nil
end
end