PistonDevelopers/dyon

Lazy invariant following argument type

bvssvni opened this issue · 4 comments

This feature was designed to help with #635.

A lazy invariant is a value that the functions returns immediately when the argument has the value. This makes it unnecessary to evaluate the rest of the arguments. When a lazy invariant is specified, the runtime checks the argument value before evaluating the rest of the arguments and calling the function. If the value matches the lazy variant, the runtime returns the value immediately.

Example (pseudocode):

use std as std

fn and(a: bool => false, b: bool) -> bool {return std::and_also(a, b)}

This makes the and function behave exactly like the && operator.

One can use lazy invariants on other types than bool and on multiple arguments:

Example:

use std as std

fn mul(a: f64 => 0, b: f64 => 0) -> f64 {return std::mul(a, b)}

When either a or b is 0, the runtime returns 0 immediately instead of calling the function and evaluating the function body. However, the argument a is evaluated before b.

Lazy invariants improve performance when evaluating arguments or function body is expensive.

// The intersection of two sets returns `[]` immediately if either set is empty.
fn intersect(a: [] => [], b: [] => []) -> [] { ... }

A list of lazy invariants can be used to handle operations on dynamical types, e.g:

fn concat(a: any => [] | "", b: any => [] | "") -> any { ... }

Normal arguments might therefore be considered lazy invariants with length zero.

The syntax ok(_), err(_), some(_) can be used to unwrap automatically without evaluating the rest of the arguments. The syntax _ is used to return the argument.

For example, the unwrap_or function is be declared such that the default value is lazy evaluated:

fn unwrap_or(var: any => ok(_) | some(_), def: any) -> any { ... }

One idea is to extend this further, to support conditional evaluation based on previous arguments.

fn if(cond: bool, tr: 'return any => if cond, fa: 'return any => if !cond) -> any {
    if cond {return tr} else {return fa}
}

More ideas on the use of _:

fn id(a: any => _) -> any { ... }
fn first(a: [] => [_, ...]) -> any { ... }
fn second(a: [] => [.., _, ...]) -> any { ... }
fn third(a: [] => [.., .., _, ...]) -> any { ... }
fn last(a: [] => [..., _]) -> any { ... }
fn second_last(a: [] => [..., _, ..]) -> any { ... }
fn foo(a: {} => {bar: _, ...}) -> any { ... }

One might also check equality with previous arguments:

fn foo(a: f64, b: => [a, _]) -> any { ... }

This returns the second item in the list of b if the first item is equal to a.

Here is a version that borrows syntax from path semantics:

// Returns `b` if it is greater than `a`.
fn foo(a: f64, b: any => (> a)) -> any { ... }
// Returns `b` if `f(b) == a`.
fn foo(f: \(any) -> any, a: any, b: any => [f] a) -> any { ... }
// Returns second item of `b` if it is an array of 2 elements
// and the first is equal to `a`.
fn foo(a: f64, b: any => [(= a), _]) -> any { ... }