Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Higher-order procedures

A procedure that takes another procedure as an argument is higher-order. It decides when the work happens; the caller decides what the work is. To accept plain lambdas and capturing closures alike, the parameter is a generic type:

#import "std/containers"

'' A procedure that takes another procedure is "higher-order". Taking it
'' as a generic F accepts plain lambdas and capturing closures alike.
sub Repeat( of F )( byval times as int32, byref action as F )
    for i as int32 = 1 to times
        action( i )
    next
end sub

dim as int32 sumOfSquares = 0
Repeat( 4, sub[ byref sumOfSquares ]( byval i as int32 )
    sumOfSquares += i * i
end sub )
print "1 + 4 + 9 + 16 ="; sumOfSquares

'' The standard containers take callables the same way.
dim scores as containers.Array( of int32 ) = { 72, 95, 41, 88, 60 }

'' SortBy asks the comparer one question: does a come before b?
containers.SortBy( scores, function( byref a as int32, _
                                     byref b as int32 ) as boolean
    return a > b
end function )

'' ForEach hands each element to an action; this one counts passes.
dim as int32 passMark = 60, passed = 0
containers.ForEach( scores, _
    sub[ byval passMark, byref passed ]( byref s as int32 )
        print s;
        if s >= passMark then passed += 1
    end sub )
print
print passed; " of"; scores.Count( ); " scores passed"

Output

1 + 4 + 9 + 16 = 30
 95 88 72 60 41
 4 of 5 scores passed

Repeat( of F ) takes its action as byref action as F. F is whatever type the argument has: here, the type of a closure that captures sumOfSquares. Inside, action( i ) calls it like any procedure. Generics covers of in full.

The standard library's containers take callables the same way:

  • containers.SortBy takes a comparer that answers one question, "does a come before b?". Answering a > b sorts from highest to lowest.
  • containers.ForEach hands each element to an action. This one captures passMark by value, because it only reads it, and passed by reference, because it updates it.

A parameter typed as a procedure pointer, like Tabulate's on the procedure pointers page, takes only lambdas that capture nothing; a closure passed to one is TKO0698: this argument does not convert to the parameter's type. The generic form takes both. Containers lists the other procedures that take a callable.

Activities

  1. Write function CountIf( of F )( values( ) as int32, byref test as F ) as int32 and use it with a lambda that tests for even numbers.
  2. Sort scores from lowest to highest, then print only the three best.