Predicate, Action and Transform
Three aliases in the prelude for the closure shapes signatures take most - a question about one value, an effect on one value, and a conversion of one value into another.
Predicate, Action and Transform name the three closure shapes that turn up in almost every signature that takes a
closure: a yes-or-no question about one value, an effect on one value, and the conversion of one value into another.
They are declared in std/core and re-exported by the prelude, so every file has them without an import. filter,
forEach and map of the standard library are written with them.
Example#
fn countWhere(numbers: List<Int>, predicate: Predicate<Int>): Int {
numbers.filter(predicate).count()
}
fn visit(names: List<String>, action: Action<String>) {
names.forEach action
}
fn labels<Output>(numbers: List<Int>, transform: Transform<Int, Output>): List<Output> {
numbers.map(transform).toList()
}
fn isEven(number: Int): Bool {
number % 2 == 0
}
const numbers = [3, 8, 5, 6]
const names = ["Ada", "Alan"]
const large = countWhere numbers { _ > 4 }
const even = countWhere numbers, isEven
const shown = labels numbers { "#{_}" }
print large // prints 3
print even // prints 2
print shown // prints ["#3", "#8", "#5", "#6"]
visit names { print "hello {_}" }
// prints hello Ada
// prints hello AlanSyntax#
public type Predicate<Value> = (value: Value) => Bool
public type Action<Value> = (value: Value) => Void
public type Transform<Input, Output> = (value: Input) => OutputRules#
An alias is transparent: it is another name for the function type, and adds no type of its own.
Predicate<Int>and(value: Int) => Boolare one type, so a value of either passes where the other is expected, andPredicate<Int>andTransform<Int, Bool>are that type as well. A diagnostic spells the shape out, because the shape is what did not fit.Does not compilefn countWhere(numbers: List<Int>, predicate: Predicate<Int>): Int { numbers.filter(predicate).count() } fn shout(text: String): String { text + "!" } const count = countWhere([1, 2], shout) // error: Expected `(value: Int64) => Bool`, found `(text: String) => String`Any closure or function of the shape fits. The parameter is called
valuein the alias, but a closure names its own parameter ({ number => number > 4 }) or uses_, and a function whose parameter has another name (isEven(number: Int)) is passed as it is. A trailing closure fills a parameter written with an alias exactly as it fills one written out, and the closure's parameter type comes from the alias.A type parameter is inferred through the alias.
labelsabove takes aTransform<Int, Output>, and the closure{ "#{_}" }makesOutputaString- the same inference the spelled-out(value: Int) => Outputgets. The output of aTransformis any type:filterMaptakes aTransform<Item, Output?>,flatMapaTransform<Item, Iterate<Output>>.A signature that takes one of the three shapes writes the alias.
predicate: Predicate<Item>says what the closure is for before it says what it looks like, and reads like the standard library.Predicateis a question, as in logic and in Java's and C#'sPredicate. It answerstrueorfalseabout the one value it is given, and asks nothing else.Actionis a closure called for its effect. It is not calledConsumer: to consume already means something in TorbScript - taking the values out of aSourceor a channel, which happens once.Transformis named after thetransformparameter ofmap. It is not calledFunctionorFunc, because those words name every function, and a closure without parameters is a function as much as this one is.A closure without parameters has no alias.
() => Valueand() => Voidare already as short as a name would be, sodo,unlessandretryspell them out.
What this is not#
Not a family of functional interfaces. There is no alias for two parameters (fold takes
(State, Item) => State), none for a var parameter (List.update takes (var element: Item) => Void), and none for
a closure that answers a Task (the step of Source.then). Those shapes are spelled out where they occur.
Not a type of its own. An alias has no members and no trait to implement: nothing that is not a closure or a
function of the shape becomes a Predicate, and a Predicate cannot be told apart from the function type it names.
Not a change to closures. A closure is still written { parameters => body }; the alias only names the type of a
parameter, a field or a binding that holds one.
Related#
- Closures - the closure literal and how its parameter types are inferred.
- Trailing closures - which parameter a trailing closure fills.
- std/core - the package that declares the three aliases.
- std/prelude - the re-exports that put them in scope everywhere.
- Idiomatic TorbScript - the habit of writing the alias.