(Advanced) Blocking Generics

You're able to define a procedure whose "blocking"-ness is generically determined by the type of the first-class procedure arg that the function is called with. Taking inspiration from Rust's Keyword Generics Initiative, a Claro procedure may be declared "Blocking-Generic" with the following syntax:

Fig 1:


# Explicitly annotate which arg(s) are blocking-generic.
blocking:pred function filter<T>(
    l: [T],
    pred: blocking? function<T -> boolean>  # <-- Accepting a blocking-generic function arg.
) -> [T] {
  return [x | x in l where pred(x)];
}

Now, with only a single implementation of your filter function, calls may be statically determined to be either a blocking or non-blocking call depending on the type of the passed pred function arg. So now, from within a Graph, you may call this "blocking-generic" function as long as you pass in a non-blocking pred function.

Fig 2:


graph function deferToBlockingGenericFn(l: [int]) -> future<[int]> {
  root noopRes <- @defer;
  # Legal call to non-blocking procedure in Graph.
  node defer   <- filter(l, (x: int) -> boolean { return x > 0; });
}

var res <-| deferToBlockingGenericFn([-1, 5, 3, 0, 2]);
print(res);

Output:

[5, 3, 2]

And of course, Claro will statically forbid calls to blocking-generic procedures when a blocking procedure is passed in:

Fig 3:


graph function deferToBlockingGenericFn(l: [int]) -> future<[int]> {
  root noopRes <- @defer;
  # Illegal call to blocking procedure in Graph.
  node defer   <- filter(l, doBlocking);
}

blocking function doBlocking(x: int) -> boolean {
  var unwrappedGraphRes <-| futures::immediateFuture(x);  # <-- Blocking unwrap.
 return unwrappedGraphRes > 0;
}

Compilation Errors:

Graph Function deferToBlockingGenericFn function<[int] -> future<[int]>> has illegal transitive dep on the following blocking procedures []. Blocking is forbidden within a Graph Function in order to avoid deadlocking.
1 Error

Note: Claro's error messaging is a work in progress - the above error message will be improved.

Note on the blocking:argName and blocking? Syntax

Claro localizes Generics only to procedure signatures. This is done with the intention of making Generics more easily understandable, such that Generics itself may be conceptualized simply as a form of "templating" (regardless of whether this is how the compiler is actually implementing the feature).

As a result, these type modifier syntaxes are restricted to being used within top-level procedure definition signatures only. In particular, you may not define a variable of a blocking-generic procedure type:

Fig 4:


# Illegal use of `blocking:...`, and `blocking?` outside of top-level Procedure definition.
var myBlockingGenericFn:
    blocking:arg1 function<|[int], blocking? function<int -> boolean>| -> [int]>;

Compilation Errors:

blocking_generics_EX4_main.claro:3: Unexpected token <:>
    blocking:arg1 function<|[int], blocking? function<int -> boolean>| -> [int]>;
            ^
Can't recover from previous error(s)
2 Errors

Note: Claro's error messaging is a work in progress - the above error message will be improved.

Lambdas Cannot Use Any Form of Generics

This has the implication that lambdas may not make use of blocking generics. But this is in line with Claro's single-use intention for lambdas, encouraging the definition of lambdas that will only be used in a single limited scope. For any cases that actually need to make use of blocking-generics, you are by definition defining a procedure that should have more than one use case, and you should define a top-level procedure instead.

First-Class References to Blocking-Generic Top-Level Procedures

You can, however, still make first-class references to top-level blocking-generic procedures in order to pass them around as data. The only restriction, is that you must statically declare which blocking variant the reference will take on:

Fig 5:


# A blocking function var, to which you may *only* pass blocking functions.
var myBlockingFn: blocking function<|[int], blocking function<int -> boolean>| -> [int]>
    = filter;

# A non-blocking function var, to which you may *only* pass non-blocking functions.
var myNonBlockingFn: function<|[int], function<int -> boolean>| -> [int]>
    = filter;