Flow.apex is a library to help you weave functions in a procedural style.
Flow.apex is created to simplify the creation of a Function. It simulates the procedural invocation of Functions, and weave them to build a larger and more complicated Function. Flow.apex acts as a bridge between the small Funcs and big custom Funcs that you create by subclassing Func.
Flow.apex has a dependency over R.apex and Script.apex.
Please include them before including Flow.apex.
Flow.apex is in fact an extension of R.apex, and it requires a fair amount of knowledge on R.apex. If you want to go deeper with Flow.apex, please do check out R.apex.
Flow.apex has a built-in FlowScript to run Funcs dynamically.
Previously, we run R.product
like this:
Object result = R.product.runN(new List<Object>{ 1, 2, 3, 4});
// Generate 24 from 1 * 2 * 3 * 4
Now with FlowScript, we have:
Object result = Flow.eval('product(1, 2, 3, 4)');
// Generate 24 from 1 * 2 * 3 * 4
FlowScript makes invocations of Funcs more natural.
A Flow is actually a Func.
Func f = new Flow()
.inputAs('a', 'b').returnInteger();
Here we create a Func of (a, b) => Integer
, with an empty body.
Or we can give it a name.
Flow f = new Flow('f')
.input('a', 'b').returnInteger();
This will add Func f
to FlowScript, so that it can be referenced in the future.
This definition creates a Func that takes a
and b
as arguments, and returns Integer
.
As a Flow is jsut a Func, we can use it wherever we can use Funcs.
Object result = f.run(1, 2); // Call the Flow(Func)
We can do variable assignment in Flows.
Flow f = new Flow()
.inputAs('a', 'b').returnInteger()
.var('num = 0');
Here we created a local variable num
with the value of 0
.
Flows have global variables and local variables. Any variable that is passed from outside is a global one. Any variable that is created inside the Flow is a local one. Variables cannot share the same names, so there is no variable overridden in Flow.apex.
We can invoke functions in Flows.
Flow f = new Flow()
.inputAs('a', 'b').returnInteger()
.var('num = add(a, b)');
Here we assign the value of num
to be the result of calling add(a, b)
.
Funcs from R.apex are imported in Flow.apex. If you want to import custom Funcs, see below.
Flow.addFunc('plus', R.add);
This will register the Func R.add
in the name of plus
. So you can refer it now in FlowScript.
Flow f = new Flow()
.inputAs('a', 'b').returnInteger()
.var('num = plus(a, b)');
returnXxx
in Flow.apex only specifies what type of result is returned. To actually return something, call doReturn(Object)
or doReturnRaw(Object)
.
Flow f = new Flow()
.inputAs('a', 'b').returnInteger()
.doReturn(0);
If no doReturn
or equivalent is added, the Flow Func will return null
.
The difference between doReturn
and doReturnRaw
is that doReturn
treats String as FlowScript while doReturnRaw
does not.
Flow f = new Flow()
.inputAs('a', 'b').returnInteger()
.doReturn('"message"');
is equivalent to
Flow f = new Flow()
.inputAs('a', 'b').returnInteger()
.doReturnRaw('message');
Flow.apex supports if
and if not
blocks.
Flow f = new Flow()
.inputAs('a', 'b').returnInteger()
.doIf(
'a == 1',
Flow.block().doReturn(1)
);
And we can append the else
block too.
Flow f = new Flow()
.inputAs('a', 'b').returnInteger()
.doIf(
'a == 1',
Flow.block().doReturn(1),
Flow.block().doReturn(2)
);
Here if
needs at least one block. A block in Flow.apex is actually a block of executable statements. We can use the blocks to extend our logic.
Flow f = new Flow()
.inputAs('a', 'b').returnInteger()
.doIf(
'a == 1',
Flow.block()
.var('b = 2')
// more logic goes on here
.doReturn(1)
);
We support two types of for
blocks in Flow.apex.
Flow f = new Flow()
.inputAs('a', 'b').returnInteger()
.doFor('i = 0; i < 10; i = i + 1', Flow.block()
.var('output = debug(i)')
);
Or
Flow f = new Flow()
.inputAs('a', 'b').returnInteger()
.doFor('i in range(0, 10)', Flow.block()
.var('output = debug(i)')
);
We can use while
blocks in Flow.apex like this.
Flow f = new Flow()
.inputAs('a', 'b').returnInteger()
.var('i = 0')
.doWhile('i < 10', Flow.block()
.var('output = debug(i)')
.var('i = i + 1')
);
We can use break
and continue
in any loop blocks.
Flow f = new Flow()
.inputAs('a', 'b').returnInteger()
.var('i = 0')
.doWhile('i < 10', Flow.block()
.doIf('i == 3', Flow.block()
.doBreak()
)
.var('output = debug(i)')
.var('i = i + 1')
);
We can use switch
in Flows too.
Flow f = new Flow()
.inputAs('a', 'b').returnInteger()
.var('word = "a"')
.doSwitch('word', new List<Object>{
'a', Flow.block().var('output = debug("Matched")'),
'b', Flow.block().var('output = debug("Not Matched")')
});
Also we can use break
in switch
blocks.
With Flow.apex, recursion is not difficult to achieve.
Flow f = new Flow()
.inputAs('n').returnInteger()
.doIf(
'n == 0',
Flow.block()
.doReturn(0)
)
.var('ret = 2 + this(n - 1)')
.doReturn('ret');
We can add debugging information anywhere we want in the block.
Flow f = new Flow()
.inputAs('a', 'b').returnInteger()
.debug();
debug
will print all of the current variables in the block.