Rework monadic-bind into function backpassing.

This is more holistic and less awkward than having monadic bind working only with some pre-defined type. Backpassing work with _any_ function, and can be implemented relatively easily by rewriting the AST on-the-fly.

  Also, it is far easier to explain than trying to explain what a monadic bind is, how its behavior differs from type to type and why it isn't generally available for any monadic type.
This commit is contained in:
KtorZ
2024-03-10 14:05:56 +01:00
parent 1f530f3b24
commit df898bf239
6 changed files with 331 additions and 97 deletions

View File

@@ -1185,6 +1185,141 @@ fn trace_if_false_ok() {
assert!(check(parse(source_code)).is_ok())
}
#[test]
fn backpassing_basic() {
let source_code = r#"
fn and_then(opt: Option<a>, then: fn(a) -> Option<b>) -> Option<b> {
when opt is {
None -> None
Some(a) -> then(a)
}
}
fn backpassing(opt_i: Option<Int>, opt_j: Option<Int>) -> Option<Int> {
let i <- and_then(opt_i)
let j <- and_then(opt_j)
Some(i + j)
}
"#;
assert!(check(parse(source_code)).is_ok())
}
#[test]
fn backpassing_interleaved_capture() {
let source_code = r#"
fn and_then(opt: Option<a>, then: fn(a) -> Option<b>) -> Option<b> {
when opt is {
None -> None
Some(a) -> then(a)
}
}
fn backpassing(opt_i: Option<Int>, opt_j: Option<Int>) -> Option<Int> {
let f = and_then(opt_i, _)
let i <- f
let g = and_then(opt_j, _)
let j <- g
Some(i + j)
}
"#;
assert!(check(parse(source_code)).is_ok())
}
#[test]
fn backpassing_patterns() {
let source_code = r#"
fn and_then(opt: Option<a>, then: fn(a) -> Option<b>) -> Option<b> {
when opt is {
None -> None
Some(a) -> then(a)
}
}
type Foo {
foo: Int,
}
fn backpassing(opt_i: Option<Foo>, opt_j: Option<Foo>) -> Option<Int> {
let Foo { foo: i } <- and_then(opt_i)
let Foo { foo: j } <- and_then(opt_j)
Some(i + j)
}
"#;
assert!(check(parse(source_code)).is_ok())
}
#[test]
fn backpassing_not_a_function() {
let source_code = r#"
fn and_then(opt: Option<a>, then: fn(a) -> Option<b>) -> Option<b> {
when opt is {
None -> None
Some(a) -> then(a)
}
}
fn backpassing(opt_i: Option<Int>, opt_j: Option<Int>) -> Option<Int> {
let i <- opt_i
let j <- and_then(opt_j)
Some(i + j)
}
"#;
assert!(matches!(
check(parse(source_code)),
Err((_, Error::NotFn { .. }))
))
}
#[test]
fn backpassing_non_exhaustive_pattern() {
let source_code = r#"
fn and_then(opt: Option<a>, then: fn(a) -> Option<b>) -> Option<b> {
when opt is {
None -> None
Some(a) -> then(a)
}
}
fn backpassing(opt_i: Option<Int>, opt_j: Option<Int>) -> Option<Int> {
let 42 <- and_then(opt_i)
let j <- and_then(opt_j)
Some(i + j)
}
"#;
assert!(matches!(
check(parse(source_code)),
Err((_, Error::NotExhaustivePatternMatch { .. }))
))
}
#[test]
fn backpassing_unsaturated_fn() {
let source_code = r#"
fn and_then(opt: Option<a>, then: fn(a) -> Option<b>) -> Option<b> {
when opt is {
None -> None
Some(a) -> then(a)
}
}
fn backpassing(opt_i: Option<Int>, opt_j: Option<Int>) -> Option<Int> {
let i <- and_then
let j <- and_then(opt_j)
Some(i + j)
}
"#;
assert!(matches!(
check(parse(source_code)),
Err((_, Error::IncorrectFieldsArity { .. }))
))
}
#[test]
fn trace_if_false_ko() {
let source_code = r#"