NAME

Sidef::Types::Block::Block - Executable code block (closure) with lexical scope and callable behavior.

DESCRIPTION

This class implements blocks (closures/anonymous functions) in Sidef. Blocks are fundamental constructs that encapsulate executable code and can capture variables from their surrounding scope. They are first-class objects that can be passed as arguments, returned from functions, and stored in variables.

A block's underlying type is either block (a plain closure, { ... }) or func (a named/multiple-dispatch function, func name(...) { ... }). Blocks with type func support multiple dispatch: several candidate signatures (with optional type and subset constraints) can share the same name, and the first candidate whose parameters match the given arguments is invoked; if none match, a detailed dispatch error is raised.

Blocks in Sidef support:

  • Lexical closures with captured variables

  • Multiple dispatch, with type- and subset-constrained parameters

  • Lazy evaluation and memoization

  • Functional programming patterns (map, grep, compose)

  • Parallel execution (threads and forks)

  • Mathematical operations (summation, product, composition)

SYNOPSIS

# Simple block creation
var square = {|n| n * n }
say(square(5))           #=> 25

# Block with multiple parameters
var add = {|a, b| a + b }
say(add(3, 7))           #=> 10

# Blocks as iterators
var nums = [1, 2, 3, 4, 5]
{|n| say(n) } << nums    # Print each number

# Functional composition
var double = {|x| x * 2 }
var inc = {|x| x + 1 }
var f = (double ∘ inc)
say(f(5))                #=> 12  # (5+1)*2

# Memoization
var fib = {|n|
    n <= 1 ? n : (fib(n-1) + fib(n-2))
}.cache
say(fib(40))             # Fast due to caching

INHERITS

Inherits methods from:

* Sidef::Object::Object

CONSTRUCTION

new

Block.new(name: name, type: type, code: code_ref, ...)

Creates a new Block object from a hash of named options. code (a Perl code reference) is the essential one; type defaults to "block" (use "func" for a multiple-dispatch function) and name defaults to "__BLOCK__". Other internal options (such as vars, for parameter/type/subset metadata) are also accepted. Typically blocks are created using brace syntax ({ ... }) or func syntax rather than by calling this constructor directly.

INVOCATION

call

block.call(*args)
block(*args)

Calls the block with the provided arguments and returns the result. For a plain block-type value this jumps straight to the underlying code; for a func-type value (or one with multiple candidate signatures), this performs multiple dispatch and validates any declared return types.

var multiply = {|a, b| a * b }
say(multiply.call(6, 7))     #=> 42

# Equivalent to:
say(multiply(6, 7))          #=> 42

run

block.run(*args)

Executes the block with the given arguments and returns the result. For func-type blocks this delegates to "call" (multiple dispatch); for plain block-type values it jumps directly to the underlying code with @_ intact.

Aliases: do

do

block.do(*args)

Executes the block with the given arguments (if any) and returns the result.

var result = {
    var x = 5
    var y = 10
    x + y
}.do

say(result)          #=> 15

Aliases: run

exec

block.exec(*args)

Runs the block with the given arguments (as "run" would), but discards its return value and returns self instead. Useful for chaining side effects.

var greet = {|name| say("Hello, #{name}!") }
var same_greet = greet.exec("Alice")   #=> prints "Hello, Alice!"
# same_greet == greet

if

block.if(condition)

If condition is truthy, runs the block and returns its result; otherwise, returns condition itself unchanged (without running the block).

var action = { say("Condition was true!") }
action.if(5 > 3)    # Executes and prints message
action.if(5 < 3)    # Does not execute; returns false

while

block.while(condition)

Runs the block repeatedly for as long as calling condition.run returns a truthy value. Returns self.

var i = 0
{
    say(i)
    i++
}.while({ i < 5 })
# Prints: 0, 1, 2, 3, 4

loop

block.loop

Runs the block in an infinite loop, until a break or return control-flow statement is encountered inside it. Returns self.

var counter = 0
{
    say(counter++)
    counter >= 5 && break
}.loop
# Prints: 0, 1, 2, 3, 4

IDENTITY BLOCKS

identity

Block.identity

Returns a shared identity block that returns its single input argument unchanged.

var id = Block.identity
say(id(42))          #=> 42

list_identity

Block.list_identity

Returns a shared identity block that returns all of its arguments as a list, unchanged.

var lid = Block.list_identity
say(lid(1, 2, 3))    #=> (1, 2, 3)

array_identity

Block.array_identity

Returns a shared identity block that collects all of its arguments into a new Array.

var aid = Block.array_identity
say(aid(1, 2, 3))    #=> [1, 2, 3]

null_identity

Block.null_identity

Returns a shared identity block that ignores its arguments and returns nothing (nil).

var nid = Block.null_identity
say(nid(1, 2, 3))    #=> nil

is_identity

block.is_identity

Returns true if the block is one of the built-in identity blocks ("identity", "list_identity", "array_identity", or "null_identity"), otherwise false. Ordinary user-defined blocks always return false, even if they behave like an identity function.

say(Block.identity.is_identity)    #=> true
say({|x| x }.is_identity)          #=> false

FUNCTIONAL COMBINATORS

compose

block_a.compose(block_b)
block_a ∘ block_b

Returns a new block representing the composition of two blocks: calling the result is equivalent to block_a(block_b(*args)).

var double = {|x| x * 2 }
var square = {|x| x ** 2 }
var f = (double ∘ square)
say(f(5))            #=> 50  # double(square(5)) = double(25) = 50

# Multiple composition
var inc = {|x| x + 1 }
var g = (square ∘ double ∘ inc)
say(g(3))            #=> 64  # square(double(inc(3))) = square(double(4)) = square(8) = 64

Aliases: operator

block_a ∘ block_b

Operator form of "compose".

Aliases: compose

repeat

block.repeat(num)
block * num

If num is a Number, calls num.times(block), running the block num times and collecting the results into an array. If num is another Block, this is equivalent to "compose" (i.e. block * other_block behaves like block ∘ other_block).

var roll_die = { 6.rand.int + 1 }
var rolls = (roll_die * 10)
say(rolls)           #=> [3, 1, 6, 2, 5, 4, 1, 6, 3, 2]

Aliases: operator *

*

block * num

Operator form of "repeat".

Aliases: repeat

cache

block.cache

Returns a memoized version of the block that caches results based on input arguments. Subsequent calls with the same arguments return cached results instead of recomputing. Modifies and returns self.

var expensive = {|n|
    say("Computing fib(#{n})")
    n <= 1 ? n : (expensive(n-1) + expensive(n-2))
}.cache

say(expensive(5))    # Computes only once per unique input
say(expensive(5))    # Returns cached result

uncache

block.uncache

Removes memoization from a previously cached block, so that subsequent calls are recomputed instead of using cached results. Modifies and returns self; has no effect if the block is not cached.

var cached = {|n| n * 2 }.cache
var uncached = cached.uncache
# uncached will recompute each time

flush_cache

block.flush_cache

Clears the memoization cache for a cached block, forcing recomputation on subsequent calls, without removing its cached status. Returns self; has no effect if the block is not cached.

var block = {|n|
    say("Computing...")
    n * 2
}.cache

block(5)            # Prints "Computing..."
block(5)            # Uses cache
block.flush_cache
block(5)            # Prints "Computing..." again

ITERATION

The methods in this section are defined on Block, so the block is always the invocant (and the left-hand operand of the corresponding operator): block.map(iterable) / block >> iterable, not the other way around. Container types such as Array or Range separately provide the more commonly seen calling convention (e.g. iterable.map{ ... }).

for

block.for(*iterables)
block << iterable

Calls the block once for each element produced by the given iterable object(s) (arrays, ranges, and so on), for side effects. If the block performs a break-like exit, iteration over the remaining iterables stops. Returns self.

var numbers = [1, 2, 3, 4, 5]
{|n| say("Number: #{n}") } << numbers
# Outputs:
# Number: 1
# Number: 2
# Number: 3
# Number: 4
# Number: 5

Aliases: each, foreach, operator <<

<<

block << iterable

Operator form of "for". The block is the left-hand operand.

Aliases: for, each, foreach

map

block.map(*iterables)
block >> iterable

Calls the block once for each element produced by the given iterable object(s), collecting the returned values into a new Array.

var numbers = [1, 2, 3, 4, 5]
var squares = ({|n| n * n } >> numbers)
say(squares)         #=> [1, 4, 9, 16, 25]

Aliases: operator >>

>>

block >> iterable

Operator form of "map". The block is the left-hand operand.

Aliases: map

grep

block.grep(*iterables)
block & iterable

Calls the block once for each element produced by the given iterable object(s), collecting into a new Array only the elements for which the block returned a truthy value.

var nums = [1, 2, 3, 4, 5, 6]
var evens = ({|n| n.is_even } & nums)
say(evens)           #=> [2, 4, 6]

Aliases: operator &

&

block & iterable

Operator form of "grep". The block is the left-hand operand.

Aliases: grep

first

block.first(n=1, range=(0..Inf))

Uses the block as a predicate and returns the first n elements of range for which it returns a truthy value, as an Array. If n is omitted, returns the single first matching element directly (not wrapped in an array).

var is_even = {|n| n.is_even }
say(is_even.first(3, 1..10))    #=> [2, 4, 6]
say(is_even.first(nil, 1..10))  #=> 2

nth

block.nth(n, range=(0..Inf))

Uses the block as a predicate and returns the n-th (1-indexed) element of range for which it returns a truthy value, or nil if no such element exists or if n is not a positive number.

var is_even = {|n| n.is_even }
say(is_even.nth(3, 1..10))      #=> 6  # the 3rd even number in the range

nest

block.nest(n, value=0)

Applies the block n times, feeding the result of each application into the next as its argument, starting from value. Returns the final result.

var double = {|x| x * 2 }
say(double.nest(3, 5))       #=> 40  # 5 -> 10 -> 20 -> 40

var inc = {|x| x + 1 }
say(inc.nest(10, 0))         #=> 10

AGGREGATION

sum

block.sum(range)

Calculates the sum of all values returned by calling the block for each value in range (equivalent to range.sum_by(block)).

var identity = {|n| n }
say(identity.sum(1..10))       #=> 55  # Sum of 1 to 10

var squares = {|n| n**2 }
say(squares.sum(1..5))         #=> 55  # 1+4+9+16+25

Aliases: Σ

prod

block.prod(range)

Calculates the product of all values returned by calling the block for each value in range (equivalent to range.prod_by(block)).

var factorial = {|n| n }
say(factorial.prod(1..5))      #=> 120  # 1*2*3*4*5

var prod = {|n| n**2 }
say(prod.prod(1..4))           #=> 576  # 1*4*9*16

Aliases: Π

SEARCHING

bsearch

block.bsearch(from=0, upto=nil)

Performs a binary search for a value satisfying the block, used as the underlying comparison function for Number's own bsearch. from defaults to 0; if upto is omitted, it is computed automatically and the search range is doubled repeatedly until a satisfying value is bracketed within it. Returns the found value, or -1 if the search space is exhausted.

var find_sqrt = {|x| x**2 <=> 50 }
say(find_sqrt.bsearch())   #=> a value x where x**2 is close to 50

bsearch_le

block.bsearch_le(from=0, upto=nil)

Like "bsearch", but delegates to Number's bsearch_le: finds the greatest value for which the block's result is less than or equal to zero.

var block = {|x| 50 - x**2 }
say(block.bsearch_le())    #=> 7  # Last x where 50-x^2 >= 0

bsearch_ge

block.bsearch_ge(from=0, upto=nil)

Like "bsearch", but delegates to Number's bsearch_ge: finds the smallest value for which the block's result is greater than or equal to zero.

var block = {|x| x**2 - 30 }
say(block.bsearch_ge())    #=> 6  # First x where x^2 >= 30

bsearch_inverse

block.bsearch_inverse(from=0, upto=nil)

Like "bsearch", but delegates to Number's bsearch_inverse: finds a value whose image under the block matches the target used by the inverse search.

var square = {|x| x**2 }
say(square.bsearch_inverse())  #=> a value x where x**2 matches the target

CONCURRENCY

ffork

block.ffork(*args)

Forks a new process and calls the block with args in the child process, serializing the result to a temporary file. Returns a Sidef::Types::Block::Fork object that can be used to wait for and retrieve the result. The "f" stands for "future fork". Blocks cannot themselves be part of the returned value (attempting to serialize a Block raises an error).

var compute = {|n|
    (1..n).sum
}

var future = compute.ffork(1000000)
# Do other work...
say(future.wait)     # Wait for and get result

Aliases: start

fork

block.fork(*args)

Forks a new process and calls the block with args in the child process (without capturing a return value). Returns a Sidef::Types::Block::Fork object wrapping the child process ID.

var forked = {
    say("Child process")
    # Do work in child
}.fork

say("Parent process, child PID: #{forked.pid}")

thread

block.thread(*args)

Runs the block with args in a new native thread (using the threads or forks module). Returns a thread handle object that can be joined (.join, .get, or .wait) to retrieve the block's result.

var compute = {|n|
    (1..n).sum
}

var t = compute.thread(1000)
# Do other work...
say(t.join)     # Wait for thread and get result

Aliases: thr

INTROSPECTION AND CONVERSION

dump

block.dump

Returns a string representation of the block (its declaration-style signature together with an internal name and address), the same representation used for automatic stringification.

var block = {|x| x * 2 }
say(block.dump)      #=> "{|x| ... }"-style representation

Aliases: to_s, to_str

time

block.time

Runs the block (with no arguments) and returns the wall-clock time it took to execute, in seconds, as a Number with sub-second precision.

var duration = {
    # Some computation
    1000.times {|i| i**2 }
}.time

say("Execution took #{duration} seconds")

capture

block.capture

Runs the block with its standard output redirected to an in-memory buffer, and returns everything the block printed as a String (decoded as UTF-8), instead of letting it go to the console.

var output = {
    say("Hello, world!")
}.capture

say(output)          #=> "Hello, world!\n"

Aliases: cap

get_value

block.get_value

Returns a plain Perl code reference that, when called, invokes the block -- automatically forwarding Perl's topicalizing $_ (or $a/$b, when set) as arguments. Used internally to let blocks act as ordinary Perl subs (for example, as comparison functions for Perl-level sorting).

EXAMPLES

Functional Programming

# Map, filter, and reduce operations
var numbers = (1..10)

var evens = numbers.grep{|n| n.is_even }
var doubled = evens.map{|n| n * 2 }
var sum = doubled.sum

say(sum)             #=> 60

Closures and Scope

func make_counter {
    var count = 0
    return {
        ++count
    }
}

var counter1 = make_counter()
var counter2 = make_counter()

say(counter1())      #=> 1
say(counter1())      #=> 2
say(counter2())      #=> 1  # Independent counter

Parallel Processing

# Process data in parallel using threads
var data = (1..8)
var threads = data.map {|n|
    { n**3 }.thread
}

var results = threads.map {|t| t.join }
say(results)         #=> [1, 8, 27, 64, 125, 216, 343, 512]

Memoization for Performance

# Recursive Fibonacci with memoization
var fib = {|n|
    n <= 1 ? n : (fib(n-1) + fib(n-2))
}.cache

say(fib(100))        # Computes efficiently

SEE ALSO