struct Proc(*T, R)

Overview

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)

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)?

  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.

Defined in:

primitives.cr
proc.cr

Constructors

Instance Method Summary

Instance methods inherited from struct Value

==(other : JSON::Any)
==(other : YAML::Any)
==(other)
==
, dup dup

Instance methods inherited from class Object

! : Bool !, !=(other) !=, !~(other) !~, ==(other) ==, ===(other : JSON::Any)
===(other : YAML::Any)
===(other)
===
, =~(other) =~, as(type : Class) as, as?(type : Class) as?, class class, dup dup, hash(hasher)
hash
hash
, in?(*values : Object) : Bool
in?(collection) : Bool
in?
, inspect : String
inspect(io : IO) : Nil
inspect
, is_a?(type : Class) : Bool is_a?, itself itself, nil? : Bool nil?, not_nil! not_nil!, pretty_inspect(width = 79, newline = "\n", indent = 0) : String pretty_inspect, pretty_print(pp : PrettyPrint) : Nil pretty_print, responds_to?(name : Symbol) : Bool responds_to?, tap(&) tap, to_json(io : IO)
to_json
to_json
, to_pretty_json(io : IO, indent : String = " ")
to_pretty_json(indent : String = " ")
to_pretty_json
, to_s : String
to_s(io : IO) : Nil
to_s
, to_yaml(io : IO)
to_yaml
to_yaml
, try(&) try, unsafe_as(type : T.class) forall T unsafe_as

Class methods inherited from class Object

from_json(string_or_io, root : String)
from_json(string_or_io)
from_json
, from_yaml(string_or_io : String | IO) from_yaml

Constructor Detail

def self.new(pointer : Pointer(Void), closure_data : Pointer(Void)) #

[View source]
def self.new(&block : self) #

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

[View source]

Instance Method Detail

def ==(other : self) #

[View source]
def ===(other : self) #

[View source]
def ===(other) #
Description copied from class Object

Case equality.

The #=== method is used in a case ... when ... end expression.

For example, this code:

case value
when x
  # something when x
when y
  # something when y
end

Is equivalent to this code:

if x === value
  # something when x
elsif y === value
  # something when y
end

Object simply implements #=== by invoking #==, but subclasses (notably Regex) can override it to provide meaningful case-equality semantics.


[View source]
def arity #

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

[View source]
def call(*args : *T) : R #

Invokes this Proc and returns the result.

add = ->(x : Int32, y : Int32) { x + y }
add.call(1, 2) # => 3

[View source]
def clone #

[View source]
def closure? #

[View source]
def closure_data #

[View source]
def hash(hasher) #

[View source]
def partial(*args : *U) forall U #

Returns a new Proc that has its first arguments fixed to the values given by args.

See Wikipedia, 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

[View source]
def pointer #

[View source]
def to_s(io : IO) : Nil #
Description copied from class Object

Appends a String representation of this object to the given IO object.

An object must never append itself to the io argument, as this will in turn call #to_s(io) on it.


[View source]