Implement List.find

`List.find : List elem, (elem -> Bool) -> Result elem [ NotFound ]*`
behaves as follows:

```
>>> List.find [1, 2, 3] (\n -> n > 2)
Ok 2
>>> List.find [1, 2, 3] (\n -> n > 4)
Err NotFound
```

We implement this as builtin in two phases. First, we call out to a
pure-llvm-lowlevel `ListFindUnsafe` that returns a record indicating
whether a satisfying element was found, and the value of that element
(the value is all null bytes if the element wasn't found). Then, we lift
that record to a `Result` via a standard construction of the can AST.

Closes #1909
This commit is contained in:
ayazhafiz 2021-11-07 20:56:46 -05:00
parent 35df58c18f
commit f65b174ab5
16 changed files with 417 additions and 8 deletions

View file

@ -1093,6 +1093,42 @@ fn call_spec(
add_loop(builder, block, state_type, init_state, loop_body)
}
ListFindUnsafe { xs } => {
let list = env.symbols[xs];
// Mark the list as being used by the "find" predicate function.
// It may be the case that all elements in the list are used by the predicate.
// Since `bag_get` assumes items are picked non-deterministically, this is
// (probably?) enough to express that usage.
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let element = builder.add_bag_get(block, bag)?;
let _bool = call_function!(builder, block, [element]);
// ListFindUnsafe returns { value: v, found: Bool=Int1 }
let output_layouts =
vec![arg_layouts[0].clone(), Layout::Builtin(Builtin::Int1)];
let output_layout = Layout::Struct(&output_layouts);
let output_type = layout_spec(builder, &output_layout)?;
// We may or may not use the element we got from the list in the output struct,
// depending on whether we found the element to satisfy the "find" predicate.
let found_branch = builder.add_block();
let output_with_element =
builder.add_unknown_with(found_branch, &[element], output_type)?;
let not_found_branch = builder.add_block();
let output_without_element =
builder.add_unknown_with(not_found_branch, &[], output_type)?;
builder.add_choice(
block,
&[
BlockExpr(found_branch, output_with_element),
BlockExpr(not_found_branch, output_without_element),
],
)
}
}
}
}

View file

@ -618,7 +618,8 @@ impl<'a> BorrowInfState<'a> {
| ListKeepIf { xs }
| ListKeepOks { xs }
| ListKeepErrs { xs }
| ListAny { xs } => {
| ListAny { xs }
| ListFindUnsafe { xs } => {
// own the list if the function wants to own the element
if !function_ps[0].borrow {
self.own_var(*xs);
@ -959,6 +960,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
arena.alloc_slice_copy(&[owned, owned, function, closure_data])
}
ListSortWith => arena.alloc_slice_copy(&[owned, function, closure_data]),
ListFindUnsafe => arena.alloc_slice_copy(&[owned, function, closure_data]),
// TODO when we have lists with capacity (if ever)
// List.append should own its first argument

View file

@ -531,7 +531,8 @@ impl<'a> Context<'a> {
| ListKeepIf { xs }
| ListKeepOks { xs }
| ListKeepErrs { xs }
| ListAny { xs } => {
| ListAny { xs }
| ListFindUnsafe { xs } => {
let borrows = [function_ps[0].borrow, FUNCTION, CLOSURE_DATA];
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);

View file

@ -4164,6 +4164,11 @@ pub fn with_hole<'a>(
match_on_closure_argument!(ListMap4, [xs, ys, zs, ws])
}
ListFindUnsafe => {
debug_assert_eq!(arg_symbols.len(), 2);
let xs = arg_symbols[0];
match_on_closure_argument!(ListFindUnsafe, [xs])
}
_ => {
let call = self::Call {
call_type: CallType::LowLevel {

View file

@ -50,6 +50,9 @@ pub enum HigherOrder {
ListAny {
xs: Symbol,
},
ListFindUnsafe {
xs: Symbol,
},
DictWalk {
xs: Symbol,
state: Symbol,
@ -71,6 +74,7 @@ impl HigherOrder {
HigherOrder::ListKeepOks { .. } => 1,
HigherOrder::ListKeepErrs { .. } => 1,
HigherOrder::ListSortWith { .. } => 2,
HigherOrder::ListFindUnsafe { .. } => 1,
HigherOrder::DictWalk { .. } => 2,
HigherOrder::ListAny { .. } => 1,
}