Merge branch 'main' of github.com:HigherOrderCo/bend

This commit is contained in:
Victor Taelin 2024-05-15 12:47:03 -03:00
commit 3e9785b707
24 changed files with 189 additions and 78 deletions

2
Cargo.lock generated
View File

@ -82,7 +82,7 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "bend"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"TSPL 0.0.9 (git+https://github.com/developedby/TSPL.git?branch=fix-hvml-bugs)",
"clap",

View File

@ -1,6 +1,6 @@
[package]
name = "bend"
version = "0.2.0"
version = "0.2.1"
edition = "2021"
[lib]

View File

@ -336,7 +336,7 @@ be turned into a tree!
The `bend` is the opposite of the `fold`: instead of consuming something, it
creates something. The way it works is, in some ways, similar to a while loop,
but, admitedly, a little bit more bureaucratic, in 3 ways:
but, admittedly, a little bit more bureaucratic, in 3 ways:
1. You must explicit an initial state.
@ -387,7 +387,7 @@ itself, forming a tree of descendants. In the end, you have a complete tree!
The second way to explain is for the devs offended by this ultra simplified
example: yes, `bend` is just a syntax-sugar for creating a recursive function,
immediatelly calling it, and assigning the result to a variable. In other words,
immediately calling it, and assigning the result to a variable. In other words,
the program above is equivalent to:
```python
@ -515,4 +515,4 @@ Benchmarks:
## ...
TODO: conver IO and so many other aspects :')
TODO: cover IO and so many other aspects :')

View File

@ -9,6 +9,7 @@
"arity",
"behaviour",
"bitand",
"Bitonic",
"builtins",
"callcc",
"chumsky",
@ -24,6 +25,7 @@
"destructures",
"desugared",
"desugars",
"devs",
"dref",
"dups",
"effectful",
@ -58,6 +60,7 @@
"nullary",
"numop",
"nums",
"OOM's",
"oper",
"opre",
"oprune",
@ -67,6 +70,7 @@
"postcondition",
"powi",
"prec",
"quadtree",
"readback",
"recursively",
"redex",
@ -111,6 +115,7 @@
"/`.`/g",
"/`..`/g",
"/`...`/g",
"/`....`/g"
"/`....`/g",
"/```.*\n.*\n```/g"
]
}
}

View File

@ -110,6 +110,7 @@ def max(a, b):
else:
return b
```
```py
// Not allowed, early return
def Foo(x):
@ -120,6 +121,7 @@ def Foo(x):
return y
```
```py
// Not allowed, one of the branches doesn't return
def Foo(a, b):
@ -226,7 +228,7 @@ bend x = 0:
```
Which binds a variable to the return of an inline recursive function.
The function `go` is available inside the `when` arm of the `bend` and calls it recursively.
The function `go` is available inside the `when` arm of the `bend` and calls it recursively.
It is possible to pass multiple state variables, which can be initialized:
@ -263,6 +265,7 @@ Where `x <- ...` performs a monadic operation.
Expects `Result` to be a type defined with `type` and a function `Result/bind` to be defined.
The monadic bind function should be of type `(Result a) -> (a -> Result b) -> Result b`, like this:
```
def Result/bind(res, nxt):
match res:
@ -273,6 +276,7 @@ def Result/bind(res, nxt):
```
Other statements are allowed inside the `do` block and it can both return a value at the end and bind a variable, like branching statements do.
```
// Also ok:
do Result:
@ -350,6 +354,29 @@ callee(expr1, expr2, arg4 = expr3, arg3 = expr4)
In case named arguments are used, they must come after the positional arguments and the function must be called with exactly the number of arguments of its definition.
### Eraser
```python
*
eraser = *
*(41 + 1) // applies 41 + 1 to `*` erasing the number and returns `*`
* = 41 + 1 // erases 41 + 1
```
The effect of an eraser is to free memory. Erasers behave like a `null`.
It's impossible to compare or match eraser values.
It is implicitly inserted for variables that have not been used:
```python
def constant(x):
return 8345
```
### Tuple
```python
@ -476,7 +503,7 @@ fold list:
List/nil
```
<div id="core-syntax"></div>
<div id="fun-syntax"></div>
# Fun Syntax

View File

@ -6,4 +6,4 @@ Tree.gen n x = switch n {
_: (Tree.node x (Tree.gen n-1 (+ (* x 2) 1)) (Tree.gen n-1 (+ (* x 2) 2)))
}
main = (Tree.gen 8 2)
main = (Tree.gen 4 1)

View File

@ -22,4 +22,4 @@ def gen(depth):
// returns the sum of 0..2^16
def main:
return sum(gen(24))
return sum(gen(16))

View File

@ -1,7 +1,7 @@
use super::{parser::TermParser, Book, Name, Num, Pattern, Term};
use crate::maybe_grow;
const BUILTINS: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/fun/builtins.hvm"));
const BUILTINS: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/fun/builtins.bend"));
pub const LIST: &str = "List";
pub const LCONS: &str = "List/cons";

View File

@ -367,7 +367,7 @@ impl<'a> TermParser<'a> {
}
// Native Number
if self.peek_one().map_or(false, |c| "0123456789+-".contains(c)) {
if self.peek_one().map_or(false, is_num_char) {
unexpected_tag(self)?;
let num = self.parse_number()?;
return Ok(Term::Num { val: num });
@ -658,10 +658,14 @@ impl<'a> Parser<'a> for TermParser<'a> {
}
}
fn is_name_char(c: char) -> bool {
pub fn is_name_char(c: char) -> bool {
c.is_ascii_alphanumeric() || c == '_' || c == '.' || c == '-' || c == '/'
}
pub fn is_num_char(c: char) -> bool {
"0123456789+-".contains(c)
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Indent {
Val(isize),
@ -766,7 +770,7 @@ pub trait ParserCommons<'a>: Parser<'a> {
fn consume_new_line(&mut self) -> ParseResult<()> {
self.skip_trivia_inline();
self.try_consume_exactly("\r");
self.consume_exactly("\n")
self.labelled(|p| p.consume_exactly("\n"), "newline")
}
/// Skips trivia, returns the number of trivia characters skipped in the last line.
@ -969,6 +973,37 @@ pub trait ParserCommons<'a>: Parser<'a> {
Ok(opr)
}
fn peek_oper(&mut self) -> Option<Op> {
let opr = if self.starts_with("+") {
Op::ADD
} else if self.starts_with("-") {
Op::SUB
} else if self.starts_with("*") {
Op::MUL
} else if self.starts_with("/") {
Op::DIV
} else if self.starts_with("%") {
Op::REM
} else if self.starts_with("<") {
Op::LTN
} else if self.starts_with(">") {
Op::GTN
} else if self.starts_with("==") {
Op::EQL
} else if self.starts_with("!=") {
Op::NEQ
} else if self.starts_with("&") {
Op::AND
} else if self.starts_with("|") {
Op::OR
} else if self.starts_with("^") {
Op::XOR
} else {
return None;
};
Some(opr)
}
fn parse_u32(&mut self) -> ParseResult<u32> {
let radix = match self.peek_many(2) {
Some("0x") => {

View File

@ -20,6 +20,9 @@ enum Used {
/// Should be pruned if `prune_all` or if it's a built-in rule
/// Otherwise, if the rule has a non-generated name, a warning about the unused def is returned
Unused,
/// An unused definition but needed for compilation when `-Oprune` is not active.
Needed,
}
type Definitions = HashMap<Name, Used>;
@ -36,6 +39,14 @@ impl Ctx<'_> {
self.book.find_used_definitions(&def.rule().body, Used::Main, &mut used);
}
if !prune_all {
for def in self.book.defs.values() {
if !def.builtin && !used.contains_key(&def.name) {
self.book.find_used_definitions(&def.rule().body, Used::Needed, &mut used);
}
}
}
for (def_name, def) in &self.book.defs {
if !def.builtin && self.book.ctrs.get(def_name).is_some() {
used.insert(def_name.clone(), Used::Adt);

View File

@ -1,6 +1,6 @@
use crate::{
diagnostics::Diagnostics,
fun::{Ctx, Name, Pattern, Term},
fun::{Ctx, Name, Term},
maybe_grow,
};
use std::collections::HashSet;
@ -41,15 +41,9 @@ impl Term {
let fun = make_fun_name(typ);
if def_names.contains(&fun) {
let mut fvs = nxt.free_vars();
pat.binds().flatten().for_each(|bind| _ = fvs.remove(bind));
let fvs = fvs.into_keys().collect::<Vec<_>>();
let nxt = fvs
.iter()
.fold(*nxt.clone(), |nxt, nam| Term::lam(Pattern::Var(Some(nam.clone())), nxt.clone()));
let nxt = Term::lam(*pat.clone(), nxt);
let term = Term::call(Term::Ref { nam: fun.clone() }, [*val.clone(), nxt]);
*self = Term::call(term, fvs.into_iter().map(|nam| Term::Var { nam }));
// TODO: come up with a strategy for forwarding free vars to prevent infinite recursion.
let nxt = Term::lam(*pat.clone(), std::mem::take(nxt));
*self = Term::call(Term::Ref { nam: fun.clone() }, [*val.clone(), nxt]);
} else {
return Err(format!("Could not find definition {} for type {}.", fun, typ));
}

View File

@ -155,7 +155,7 @@ impl Expr {
go(entry, substitutions, id);
}
}
Expr::None | Expr::Str { .. } | Expr::Var { .. } | Expr::Chn { .. } | Expr::Num { .. } => {}
Expr::Eraser | Expr::Str { .. } | Expr::Var { .. } | Expr::Chn { .. } | Expr::Num { .. } => {}
}
}
let mut substitutions = Substitutions::new();

View File

@ -3,19 +3,19 @@ mod order_kwargs;
pub mod parser;
pub mod to_fun;
use crate::fun::{CtrField, Name, Op};
use crate::fun::{CtrField, Name, Num, Op};
use interner::global::GlobalString;
#[derive(Clone, Debug)]
pub enum Expr {
// "None"
None,
// "*"
Eraser,
// [a-zA-Z_]+
Var { nam: Name },
// "$" [a-zA-Z_]+
Chn { nam: Name },
// [0-9_]+
Num { val: u32 },
Num { val: Num },
// {fun}({args},{kwargs},)
Call { fun: Box<Expr>, args: Vec<Expr>, kwargs: Vec<(Name, Expr)> },
// "lambda" {names}* ":" {bod}
@ -48,6 +48,8 @@ pub struct MatchArm {
#[derive(Clone, Debug)]
pub enum AssignPattern {
// "*"
Eraser,
// [a-zA-Z_]+
Var(Name),
// "$" [a-zA-Z_]+

View File

@ -150,7 +150,7 @@ impl Expr {
}
}
Expr::MapGet { .. }
| Expr::None
| Expr::Eraser
| Expr::Var { .. }
| Expr::Chn { .. }
| Expr::Num { .. }

View File

@ -1,7 +1,7 @@
use crate::{
fun::{
parser::{Indent, ParseResult, ParserCommons},
Adt, Book, CtrField, Name, Op, STRINGS,
parser::{is_num_char, Indent, ParseResult, ParserCommons},
Adt, Book, CtrField, Name, Num, Op, STRINGS,
},
imp::{AssignPattern, Definition, Enum, Expr, InPlaceOp, MatchArm, Stmt, Variant},
maybe_grow,
@ -9,7 +9,11 @@ use crate::{
use TSPL::Parser;
const PREC: &[&[Op]] =
&[&[Op::EQL, Op::NEQ], &[Op::LTN], &[Op::GTN], &[Op::ADD, Op::SUB], &[Op::MUL, Op::DIV]];
&[&[Op::OR], &[Op::XOR], &[Op::AND], &[Op::EQL, Op::NEQ], &[Op::LTN], &[Op::GTN], &[Op::ADD, Op::SUB], &[
Op::MUL,
Op::DIV,
Op::REM,
]];
pub struct PyParser<'i> {
pub input: &'i str,
@ -71,6 +75,7 @@ impl<'a> PyParser<'a> {
let head = self.parse_expr(false)?;
self.skip_trivia();
if self.starts_with(",") {
// A Tuple
let mut els = vec![head];
while self.try_consume(",") {
els.push(self.parse_expr(false)?);
@ -79,6 +84,7 @@ impl<'a> PyParser<'a> {
Expr::Tup { els }
} else {
self.consume(")")?;
// A parenthesized expression
head
}
}
@ -89,7 +95,7 @@ impl<'a> PyParser<'a> {
if self.starts_with(":") { self.parse_map_init(head)? } else { self.parse_sup(head)? }
}
'[' => self.list_or_comprehension()?,
'`' => Expr::Num { val: self.parse_quoted_symbol()? },
'`' => Expr::Num { val: Num::U24(self.parse_quoted_symbol()?) },
'\"' => {
let str = self.parse_quoted_string()?;
let val = STRINGS.get(str);
@ -100,8 +106,12 @@ impl<'a> PyParser<'a> {
let nam = self.parse_bend_name()?;
Expr::Chn { nam }
}
c if c.is_ascii_digit() => {
let val = self.parse_u32()?;
'*' => {
self.advance_one();
Expr::Eraser
}
c if is_num_char(c) => {
let val = self.parse_number()?;
Expr::Num { val }
}
// var or postfix
@ -284,9 +294,9 @@ impl<'a> PyParser<'a> {
}
let mut lhs = self.parse_infix_expr(prec + 1, inline)?;
self.skip_trivia_maybe_inline(inline);
while let Some(op) = self.get_op() {
while let Some(op) = self.peek_oper() {
if PREC[prec].iter().any(|r| *r == op) {
let op = self.parse_oper()?;
self.parse_oper()?;
let rhs = self.parse_infix_expr(prec + 1, inline)?;
lhs = Expr::Bin { op, lhs: Box::new(lhs), rhs: Box::new(rhs) };
self.advance_trivia_inline();
@ -298,31 +308,6 @@ impl<'a> PyParser<'a> {
})
}
fn get_op(&mut self) -> Option<Op> {
let ret = if self.starts_with("+") {
Op::ADD
} else if self.starts_with("-") {
Op::SUB
} else if self.starts_with("*") {
Op::MUL
} else if self.starts_with("/") {
Op::DIV
} else if self.starts_with("%") {
Op::REM
} else if self.starts_with("<") {
Op::LTN
} else if self.starts_with(">") {
Op::GTN
} else if self.starts_with("==") {
Op::EQL
} else if self.starts_with("!=") {
Op::NEQ
} else {
return None;
};
Some(ret)
}
fn consume_indent_at_most(&mut self, expected: Indent) -> ParseResult<Indent> {
let got = self.advance_newlines();
match (expected, got) {
@ -390,6 +375,9 @@ impl<'a> PyParser<'a> {
let val = self.parse_expr(true)?;
self.skip_trivia_inline();
self.try_consume_exactly(";");
if !self.is_eof() {
self.consume_new_line()?;
}
let nxt_indent = self.advance_newlines();
if nxt_indent == *indent {
let (nxt, nxt_indent) = self.parse_statement(indent)?;
@ -460,6 +448,9 @@ impl<'a> PyParser<'a> {
let term = self.parse_expr(true)?;
self.skip_trivia_inline();
self.try_consume_exactly(";");
if !self.is_eof() {
self.consume_new_line()?;
}
let indent = self.advance_newlines();
Ok((Stmt::Return { term: Box::new(term) }, indent))
}
@ -766,6 +757,11 @@ impl<'a> PyParser<'a> {
/// | <nam> "[" <expr> "]"
/// | <nam>
fn parse_assign_pattern(&mut self) -> ParseResult<AssignPattern> {
// Eraser pattern
if self.starts_with("*") {
self.advance_one();
return Ok(AssignPattern::Eraser);
}
// Tup pattern
if self.starts_with("(") {
let binds = self.list_like(|p| p.parse_assign_pattern(), "(", ")", ",", true, 1)?;

View File

@ -22,6 +22,7 @@ impl Definition {
impl AssignPattern {
pub fn into_fun(self) -> fun::Pattern {
match self {
AssignPattern::Eraser => fun::Pattern::Var(None),
AssignPattern::Var(name) => fun::Pattern::Var(Some(name)),
AssignPattern::Chn(name) => fun::Pattern::Chn(name),
AssignPattern::Tup(names) => fun::Pattern::Fan(
@ -273,10 +274,10 @@ impl Stmt {
impl Expr {
pub fn to_fun(self) -> fun::Term {
match self {
Expr::None => fun::Term::Era,
Expr::Eraser => fun::Term::Era,
Expr::Var { nam } => fun::Term::Var { nam },
Expr::Chn { nam } => fun::Term::Link { nam },
Expr::Num { val } => fun::Term::Num { val: fun::Num::U24(val) },
Expr::Num { val } => fun::Term::Num { val },
Expr::Call { fun, args, kwargs } => {
assert!(kwargs.is_empty());
let args = args.into_iter().map(Self::to_fun);

View File

@ -237,6 +237,7 @@ fn parse_file() {
ctx.book.encode_adts();
ctx.book.encode_builtins();
ctx.resolve_refs().expect("Resolve refs");
ctx.desugar_match_defs().expect("Desugar match defs");
ctx.prune(false);
Ok(book.to_string())
})

View File

@ -65,7 +65,12 @@ def bnd():
else:
return List/nil();
def era():
* = (2 + 3)
the_expr_killer = *
return the_expr_killer(9)
def main():
do IO:
x <- IO.read();
return x;
return x;

View File

@ -2,4 +2,4 @@
source: tests/golden_tests.rs
input_file: examples/bitonic_sort.bend
---
523776
16515072

File diff suppressed because one or more lines are too long

View File

@ -4,9 +4,9 @@ input_file: tests/golden_tests/parse_file/imp_map.bend
---
(Map/empty) = Map/leaf
(Map/get map key) = match map = map { Map/leaf: (*, map); Map/node: switch _ = (== 0 key) { 0: switch _ = (% key 2) { 0: let (got, rest) = (Map/get map.left (/ key 2)); (got, (Map/node map.value rest map.right)); _ _-1: let (got, rest) = (Map/get map.right (/ key 2)); (got, (Map/node map.value map.left rest)); }; _ _-1: (map.value, map); }; }
(Map/get) = λ%arg0 λ%arg1 use key = %arg1; use map = %arg0; match map = map { Map/leaf: (*, map); Map/node: switch _ = (== 0 key) { 0: switch _ = (% key 2) { 0: let (got, rest) = (Map/get map.left (/ key 2)); (got, (Map/node map.value rest map.right)); _ _-1: let (got, rest) = (Map/get map.right (/ key 2)); (got, (Map/node map.value map.left rest)); }; _ _-1: (map.value, map); }; }
(Map/set map key value) = match map = map { Map/node: switch _ = (== 0 key) { 0: switch _ = (% key 2) { 0: (Map/node map.value (Map/set map.left (/ key 2) value) map.right); _ _-1: (Map/node map.value map.left (Map/set map.right (/ key 2) value)); }; _ _-1: (Map/node value map.left map.right); }; Map/leaf: switch _ = (== 0 key) { 0: switch _ = (% key 2) { 0: (Map/node * (Map/set Map/leaf (/ key 2) value) Map/leaf); _ _-1: (Map/node * Map/leaf (Map/set Map/leaf (/ key 2) value)); }; _ _-1: (Map/node value Map/leaf Map/leaf); }; }
(Map/set) = λ%arg0 λ%arg1 λ%arg2 use value = %arg2; use key = %arg1; use map = %arg0; match map = map { Map/node: switch _ = (== 0 key) { 0: switch _ = (% key 2) { 0: (Map/node map.value (Map/set map.left (/ key 2) value) map.right); _ _-1: (Map/node map.value map.left (Map/set map.right (/ key 2) value)); }; _ _-1: (Map/node value map.left map.right); }; Map/leaf: switch _ = (== 0 key) { 0: switch _ = (% key 2) { 0: (Map/node * (Map/set Map/leaf (/ key 2) value) Map/leaf); _ _-1: (Map/node * Map/leaf (Map/set Map/leaf (/ key 2) value)); }; _ _-1: (Map/node value Map/leaf Map/leaf); }; }
(main) = let x = (Map/set (Map/set Map/empty 2 1) 3 2); let (map/get%1, x) = (Map/get x 2); let y = (id map/get%1); let z = 4; let x = (Map/set x z 4); let (map/get%0, x) = (Map/get x z); (+ y map/get%0)

View File

@ -2,28 +2,36 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/parse_file/imp_program.bend
---
(Map/empty) = Map/leaf
(Map/get) = λ%arg0 λ%arg1 use key = %arg1; use map = %arg0; match map = map { Map/leaf: (*, map); Map/node: switch _ = (== 0 key) { 0: switch _ = (% key 2) { 0: let (got, rest) = (Map/get map.left (/ key 2)); (got, (Map/node map.value rest map.right)); _ _-1: let (got, rest) = (Map/get map.right (/ key 2)); (got, (Map/node map.value map.left rest)); }; _ _-1: (map.value, map); }; }
(Map/set) = λ%arg0 λ%arg1 λ%arg2 use value = %arg2; use key = %arg1; use map = %arg0; match map = map { Map/node: switch _ = (== 0 key) { 0: switch _ = (% key 2) { 0: (Map/node map.value (Map/set map.left (/ key 2) value) map.right); _ _-1: (Map/node map.value map.left (Map/set map.right (/ key 2) value)); }; _ _-1: (Map/node value map.left map.right); }; Map/leaf: switch _ = (== 0 key) { 0: switch _ = (% key 2) { 0: (Map/node * (Map/set Map/leaf (/ key 2) value) Map/leaf); _ _-1: (Map/node * Map/leaf (Map/set Map/leaf (/ key 2) value)); }; _ _-1: (Map/node value Map/leaf Map/leaf); }; }
(symbols) = let x = (Map/set (Map/set Map/empty 49 5) 2 3); let x = (Map/set x 49 2); let x = (Map/set x 2 3); let (map/get%0, x) = (Map/get x 49); (+ map/get%0 8293490)
(mk_point) = (Point/Point 1 2)
(identity x) = x
(identity) = λ%arg0 use x = %arg0; x
(inc n) = let n = (+ n 1); n
(inc) = λ%arg0 use n = %arg0; let n = (+ n 1); n
(lam) = λx λy x
(do_match b) = match b = b { Bool/True: 1; Bool/False: 0; }
(do_match) = λ%arg0 use b = %arg0; match b = b { Bool/True: 1; Bool/False: 0; }
(true) = Bool/True
(fib n) = switch %pred = (< n 2) { 0: (+ (fib (- n 1)) (fib (- n 2))); _ %pred-1: n; }
(fib) = λ%arg0 use n = %arg0; switch %pred = (< n 2) { 0: (+ (fib (- n 1)) (fib (- n 2))); _ %pred-1: n; }
(swt n) = switch n = n { 0: 42; _ n-1: 1; }
(swt) = λ%arg0 use n = %arg0; switch n = n { 0: 42; _ n-1: 1; }
(fld list) = fold list = list { List/cons: 1; List/nil: 2; }
(fld) = λ%arg0 use list = %arg0; fold list = list { List/cons: 1; List/nil: 2; }
(bnd) = bend x = 0, while (< x 10) { (List/cons x (go (+ x 1))) } then { List/nil }
(era) = let * = (+ 2 3); let the_expr_killer = *; (the_expr_killer 9)
(main) = do IO { ask x = IO.read; x }
(List/cons) = λhead λtail λList/cons λList/nil (List/cons head tail)

View File

@ -2,4 +2,30 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/recursive_bind.bend
---
λa (a 0)
Errors:
The following functions contain recursive cycles incompatible with HVM's strict evaluation:
* Foo -> Foo
The greedy eager evaluation of HVM may cause infinite loops.
Refactor these functions to use lazy references instead of direct function calls.
A reference is strict when it's being called ('(Foo x)') or when it's used non-linearly ('let x = Foo; (x x)').
It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x = Foo; (x 0)').
Try one of these strategies:
- Use pattern matching with 'match', 'fold', and 'bend' to automatically lift expressions to lazy references.
- Replace direct calls with combinators. For example, change:
'Foo = λa λb (b (λc (Foo a c)) a)'
to:
'Foo = λa λb (b (λc λa (Foo a c)) (λa a) a)'
which is lifted to:
'Foo = λa λb (b Foo__C1 Foo_C2 a)'
- Replace non-linear 'let' expressions with 'use' expressions. For example, change:
'Foo = λf let x = Foo; (f x x)'
to:
'Foo = λf use x = Foo; (f x x)'
which inlines to:
'Foo = λf (f Foo Foo)'
- If disabled, re-enable the default 'float-combinators' and 'linearize-matches' compiler options.
For more information, visit: https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md.
To disable this check, use the "-Arecursion-cycle" compiler option.