From ad4f1bba95a686e3566db4dfb4ad08e150f16f25 Mon Sep 17 00:00:00 2001 From: imaqtkatt Date: Fri, 22 Mar 2024 10:21:48 -0300 Subject: [PATCH] Update recursion error message and update docs --- docs/lazy-definitions.md | 6 +++-- src/hvmc_net/mutual_recursion.message | 9 +++---- src/hvmc_net/mutual_recursion.rs | 24 ++++++++++++++----- ...le_o_all__repeated_name_trucation.hvm.snap | 10 ++++---- .../mutual_recursion__a_b_c.hvm.snap | 11 ++++----- .../mutual_recursion__multiple.hvm.snap | 17 +++++++------ .../mutual_recursion__odd_even.hvm.snap | 10 ++++---- 7 files changed, 50 insertions(+), 37 deletions(-) diff --git a/docs/lazy-definitions.md b/docs/lazy-definitions.md index 8ebeec66b..10c0c141c 100644 --- a/docs/lazy-definitions.md +++ b/docs/lazy-definitions.md @@ -1,8 +1,8 @@ # Making recursive definitions lazy -In strict-mode, terms that use recursive terms will unroll indefinitely. +In strict-mode, some types of recursive terms will unroll indefinitely. -This is a simple piece of code that works on many other functional programming languages, but hangs on HVM: +This is a simple piece of code that works on many other functional programming languages, including hvm's lazy-mode, but hangs on strict-mode: ```rust Cons = λx λxs λcons λnil (cons x xs) @@ -55,6 +55,8 @@ This code will work as expected, since `cons` and `nil` are lambdas without free It's recommended to use a [supercombinator](https://en.wikipedia.org/wiki/Supercombinator) formulation to make terms be unrolled lazily, preventing infinite expansion in recursive function bodies. +If you have a set of mutually recursive functions, you only need to make one of the steps lazy. This might be useful when doing micro-optimizations, since it's possible to avoid part of the small performance cost of linearizing lambdas. + ### Automatic optimization HVM-lang carries out [match linearization](compiler-options#linearize-matches) and [combinator floating](compiler-options#float-combinators) optimizations, enabled through the CLI, which are active by default in strict mode. diff --git a/src/hvmc_net/mutual_recursion.message b/src/hvmc_net/mutual_recursion.message index ff037e39a..32eff5cb2 100644 --- a/src/hvmc_net/mutual_recursion.message +++ b/src/hvmc_net/mutual_recursion.message @@ -1,5 +1,6 @@ -Seems like you're trying to run some recursive function(s) on strict-mode: -{refs} +Seems like you're trying to run some recursive function(s) on strict-mode. +The following recursive cycles are not compatible with strict-mode the way these functions were written: +{cycles} Due to the ultra-greedy nature of strict-mode, that might result in infinite loops. If the float-combinators optimization is not on, we recommend activating it. @@ -14,9 +15,9 @@ Just append the `-L` option to `HVM-Lang`, and it will run in lazy-mode. It has When a function reference is in head position of an application or is duplicated by the non-linear use of a `let` expression it will be greedly expanded, leading to an infinite loop. If the application is used written as a combinator, it will automatically lifted to a lazy reference, which usually breaks the recursion cycle. Alternatively, by using the built-in `data` and `match` syntax, hvm-lang will try to do this automatically. -(e.g. If pattern matching on scott-encoded ADTs, write @a @x(x @a (Foo a) a) instead of @a @x(x (Foo a))) +(e.g. If pattern matching on scott-encoded ADTs, write '@a @x(x @a (Foo a) a)' instead of '@a @x(x (Foo a))') For let expressions where the variable is non-linear (used more than once), you can instead employ `use` expressions to statically duplicate the offending recursive term. -(e.g. write Foo = @f use x = Foo; (f x x) instead of Foo = @f let x = Foo; (f x x)) +(e.g. write 'Foo = @f use x = Foo; (f x x)' instead of 'Foo = @f let x = Foo; (f x x)') See here for more info: https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md. diff --git a/src/hvmc_net/mutual_recursion.rs b/src/hvmc_net/mutual_recursion.rs index 83b72ef31..0d304deae 100644 --- a/src/hvmc_net/mutual_recursion.rs +++ b/src/hvmc_net/mutual_recursion.rs @@ -1,4 +1,4 @@ -use crate::diagnostics::{Diagnostics, WarningType}; +use crate::diagnostics::{Diagnostics, WarningType, ERR_INDENT_SIZE}; use hvmc::ast::{Book, Tree}; use indexmap::{IndexMap, IndexSet}; use std::fmt::Debug; @@ -17,18 +17,30 @@ pub fn check_cycles(book: &Book, diagnostics: &mut Diagnostics) -> Result<(), Di let cycles = graph.cycles(); if !cycles.is_empty() { - let msg = format!(include_str!("mutual_recursion.message"), refs = show_refs(&cycles)); + let msg = format!(include_str!("mutual_recursion.message"), cycles = show_cycles(&cycles)); diagnostics.add_book_warning(msg.as_str(), WarningType::MutualRecursionCycle); } diagnostics.fatal(()) } -fn show_refs(cycles: &[Vec]) -> String { - use itertools::Itertools; +fn show_cycles(cycles: &[Vec]) -> String { + let tail = &format!("\n{:ERR_INDENT_SIZE$}* ...", ""); + let tail = if cycles.len() > 5 { tail } else { "" }; - let cycles = cycles.concat(); - cycles.iter().take(5).join("\n") + if cycles.len() > 5 { "\n..." } else { "" } + let mut cycles = cycles + .iter() + .take(5) + .map(|cycle| { + let cycle_str = cycle.iter().chain(cycle.first()).cloned().collect::>().join(" -> "); + format!("{:ERR_INDENT_SIZE$}* {}", "", cycle_str) + }) + .collect::>() + .join("\n"); + + cycles.push_str(tail); + + cycles } impl Graph { diff --git a/tests/snapshots/compile_file_o_all__repeated_name_trucation.hvm.snap b/tests/snapshots/compile_file_o_all__repeated_name_trucation.hvm.snap index 7f7eaeef0..fb88489e4 100644 --- a/tests/snapshots/compile_file_o_all__repeated_name_trucation.hvm.snap +++ b/tests/snapshots/compile_file_o_all__repeated_name_trucation.hvm.snap @@ -3,9 +3,9 @@ source: tests/golden_tests.rs input_file: tests/golden_tests/compile_file_o_all/repeated_name_trucation.hvm --- Warnings: -Seems like you're trying to run some recursive function(s) on strict-mode: -long_name_that_truncates -long_name_that_truncates$C0 +Seems like you're trying to run some recursive function(s) on strict-mode. +The following recursive cycles are not compatible with strict-mode the way these functions were written: + * long_name_that_truncates -> long_name_that_truncates$C0 -> long_name_that_truncates Due to the ultra-greedy nature of strict-mode, that might result in infinite loops. If the float-combinators optimization is not on, we recommend activating it. @@ -20,10 +20,10 @@ Just append the `-L` option to `HVM-Lang`, and it will run in lazy-mode. It has When a function reference is in head position of an application or is duplicated by the non-linear use of a `let` expression it will be greedly expanded, leading to an infinite loop. If the application is used written as a combinator, it will automatically lifted to a lazy reference, which usually breaks the recursion cycle. Alternatively, by using the built-in `data` and `match` syntax, hvm-lang will try to do this automatically. -(e.g. If pattern matching on scott-encoded ADTs, write @a @x(x @a (Foo a) a) instead of @a @x(x (Foo a))) +(e.g. If pattern matching on scott-encoded ADTs, write '@a @x(x @a (Foo a) a)' instead of '@a @x(x (Foo a))') For let expressions where the variable is non-linear (used more than once), you can instead employ `use` expressions to statically duplicate the offending recursive term. -(e.g. write Foo = @f use x = Foo; (f x x) instead of Foo = @f let x = Foo; (f x x)) +(e.g. write 'Foo = @f use x = Foo; (f x x)' instead of 'Foo = @f let x = Foo; (f x x)') See here for more info: https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md. diff --git a/tests/snapshots/mutual_recursion__a_b_c.hvm.snap b/tests/snapshots/mutual_recursion__a_b_c.hvm.snap index 7428ca953..2f1126bbc 100644 --- a/tests/snapshots/mutual_recursion__a_b_c.hvm.snap +++ b/tests/snapshots/mutual_recursion__a_b_c.hvm.snap @@ -3,10 +3,9 @@ source: tests/golden_tests.rs input_file: tests/golden_tests/mutual_recursion/a_b_c.hvm --- Errors: -Seems like you're trying to run some recursive function(s) on strict-mode: -A -B -C +Seems like you're trying to run some recursive function(s) on strict-mode. +The following recursive cycles are not compatible with strict-mode the way these functions were written: + * A -> B -> C -> A Due to the ultra-greedy nature of strict-mode, that might result in infinite loops. If the float-combinators optimization is not on, we recommend activating it. @@ -21,9 +20,9 @@ Just append the `-L` option to `HVM-Lang`, and it will run in lazy-mode. It has When a function reference is in head position of an application or is duplicated by the non-linear use of a `let` expression it will be greedly expanded, leading to an infinite loop. If the application is used written as a combinator, it will automatically lifted to a lazy reference, which usually breaks the recursion cycle. Alternatively, by using the built-in `data` and `match` syntax, hvm-lang will try to do this automatically. -(e.g. If pattern matching on scott-encoded ADTs, write @a @x(x @a (Foo a) a) instead of @a @x(x (Foo a))) +(e.g. If pattern matching on scott-encoded ADTs, write '@a @x(x @a (Foo a) a)' instead of '@a @x(x (Foo a))') For let expressions where the variable is non-linear (used more than once), you can instead employ `use` expressions to statically duplicate the offending recursive term. -(e.g. write Foo = @f use x = Foo; (f x x) instead of Foo = @f let x = Foo; (f x x)) +(e.g. write 'Foo = @f use x = Foo; (f x x)' instead of 'Foo = @f let x = Foo; (f x x)') See here for more info: https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md. diff --git a/tests/snapshots/mutual_recursion__multiple.hvm.snap b/tests/snapshots/mutual_recursion__multiple.hvm.snap index e0cd9fa98..f2c4bb75e 100644 --- a/tests/snapshots/mutual_recursion__multiple.hvm.snap +++ b/tests/snapshots/mutual_recursion__multiple.hvm.snap @@ -3,13 +3,12 @@ source: tests/golden_tests.rs input_file: tests/golden_tests/mutual_recursion/multiple.hvm --- Errors: -Seems like you're trying to run some recursive function(s) on strict-mode: -A -B -C -H -I -... +Seems like you're trying to run some recursive function(s) on strict-mode. +The following recursive cycles are not compatible with strict-mode the way these functions were written: + * A -> B -> C -> A + * H -> I -> H + * M -> M + * N -> N Due to the ultra-greedy nature of strict-mode, that might result in infinite loops. If the float-combinators optimization is not on, we recommend activating it. @@ -24,9 +23,9 @@ Just append the `-L` option to `HVM-Lang`, and it will run in lazy-mode. It has When a function reference is in head position of an application or is duplicated by the non-linear use of a `let` expression it will be greedly expanded, leading to an infinite loop. If the application is used written as a combinator, it will automatically lifted to a lazy reference, which usually breaks the recursion cycle. Alternatively, by using the built-in `data` and `match` syntax, hvm-lang will try to do this automatically. -(e.g. If pattern matching on scott-encoded ADTs, write @a @x(x @a (Foo a) a) instead of @a @x(x (Foo a))) +(e.g. If pattern matching on scott-encoded ADTs, write '@a @x(x @a (Foo a) a)' instead of '@a @x(x (Foo a))') For let expressions where the variable is non-linear (used more than once), you can instead employ `use` expressions to statically duplicate the offending recursive term. -(e.g. write Foo = @f use x = Foo; (f x x) instead of Foo = @f let x = Foo; (f x x)) +(e.g. write 'Foo = @f use x = Foo; (f x x)' instead of 'Foo = @f let x = Foo; (f x x)') See here for more info: https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md. diff --git a/tests/snapshots/mutual_recursion__odd_even.hvm.snap b/tests/snapshots/mutual_recursion__odd_even.hvm.snap index d3eb08a3a..b51ed406c 100644 --- a/tests/snapshots/mutual_recursion__odd_even.hvm.snap +++ b/tests/snapshots/mutual_recursion__odd_even.hvm.snap @@ -3,9 +3,9 @@ source: tests/golden_tests.rs input_file: tests/golden_tests/mutual_recursion/odd_even.hvm --- Errors: -Seems like you're trying to run some recursive function(s) on strict-mode: -isEven -isOdd +Seems like you're trying to run some recursive function(s) on strict-mode. +The following recursive cycles are not compatible with strict-mode the way these functions were written: + * isEven -> isOdd -> isEven Due to the ultra-greedy nature of strict-mode, that might result in infinite loops. If the float-combinators optimization is not on, we recommend activating it. @@ -20,9 +20,9 @@ Just append the `-L` option to `HVM-Lang`, and it will run in lazy-mode. It has When a function reference is in head position of an application or is duplicated by the non-linear use of a `let` expression it will be greedly expanded, leading to an infinite loop. If the application is used written as a combinator, it will automatically lifted to a lazy reference, which usually breaks the recursion cycle. Alternatively, by using the built-in `data` and `match` syntax, hvm-lang will try to do this automatically. -(e.g. If pattern matching on scott-encoded ADTs, write @a @x(x @a (Foo a) a) instead of @a @x(x (Foo a))) +(e.g. If pattern matching on scott-encoded ADTs, write '@a @x(x @a (Foo a) a)' instead of '@a @x(x (Foo a))') For let expressions where the variable is non-linear (used more than once), you can instead employ `use` expressions to statically duplicate the offending recursive term. -(e.g. write Foo = @f use x = Foo; (f x x) instead of Foo = @f let x = Foo; (f x x)) +(e.g. write 'Foo = @f use x = Foo; (f x x)' instead of 'Foo = @f let x = Foo; (f x x)') See here for more info: https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md.