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