Merge pull request #3568 from rtfeldman/list-maptry

This commit is contained in:
Ayaz 2022-07-18 15:22:38 -05:00 committed by GitHub
commit d5f36a6780
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 113 additions and 33 deletions

View File

@ -38,6 +38,7 @@ interface List
min,
max,
map4,
mapTry,
dropFirst,
joinMap,
any,
@ -858,6 +859,32 @@ split = \elements, userSplitIndex ->
{ before, others }
## Like [List.map], except the transformation function returns a [Result].
## If that function ever returns `Err`, [mapTry] immediately returns that `Err`.
## If it returns `Ok` for every element, [mapTry] returns `Ok` with the transformed list.
mapTry : List elem, (elem -> Result ok err) -> Result (List ok) err
mapTry = \list, toResult ->
walkTry list [] \state, elem ->
Result.map (toResult elem) \ok ->
List.append state ok
## This is the same as `iterate` but with Result instead of [Continue, Break].
## Using `Result` saves a conditional in `mapTry`.
## It might be useful to expose this in userspace?
walkTry : List elem, state, (state, elem -> Result state err) -> Result state err
walkTry = \list, init, func ->
walkTryHelp list init func 0 (List.len list)
## internal helper
walkTryHelp : List elem, state, (state, elem -> Result state err), Nat, Nat -> Result state err
walkTryHelp = \list, state, f, index, length ->
if index < length then
when f state (List.getUnsafe list index) is
Ok nextState -> walkTryHelp list nextState f (index + 1) length
Err b -> Err b
else
Ok state
## Primitive for iterating over a List, being able to decide at every element whether to continue
iterate : List elem, s, (s, elem -> [Continue s, Break b]) -> [Continue s, Break b]
iterate = \list, init, func ->

View File

@ -110,6 +110,10 @@ flags! {
/// Print to stderr when a runtime error function is generated.
ROC_PRINT_RUNTIME_ERROR_GEN
/// Generate a layout error when an unbound type variable is found, rather than generating the
/// void layout.
ROC_NO_UNBOUND_LAYOUT
// ===LLVM Gen===
/// Prints LLVM function verification output.

View File

@ -31,6 +31,11 @@ impl Wasm32Sized for () {
const ALIGN_OF_WASM: usize = 0;
}
impl Wasm32Sized for std::convert::Infallible {
const SIZE_OF_WASM: usize = 0;
const ALIGN_OF_WASM: usize = 0;
}
impl Wasm32Sized for RocStr {
const SIZE_OF_WASM: usize = 12;
const ALIGN_OF_WASM: usize = 4;

View File

@ -1290,6 +1290,7 @@ define_builtins! {
66 LIST_APPEND_UNSAFE: "appendUnsafe"
67 LIST_SUBLIST_LOWLEVEL: "sublistLowlevel"
68 LIST_CAPACITY: "capacity"
69 LIST_MAP_TRY: "mapTry"
}
7 RESULT: "Result" => {
0 RESULT_RESULT: "Result" // the Result.Result type alias

View File

@ -1227,12 +1227,21 @@ impl<'a> Layout<'a> {
fn new_help<'b>(
env: &mut Env<'a, 'b>,
var: Variable,
_var: Variable,
content: Content,
) -> Result<Self, LayoutProblem> {
use roc_types::subs::Content::*;
match content {
FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)),
FlexVar(_) | RigidVar(_) => {
roc_debug_flags::dbg_do!(roc_debug_flags::ROC_NO_UNBOUND_LAYOUT, {
return Err(LayoutProblem::UnresolvedTypeVar(_var));
});
// If we encounter an unbound type var (e.g. `*` or `a`)
// then it's zero-sized; In the future we may drop this argument
// completely, but for now we represent it with the empty tag union
Ok(Layout::VOID)
}
FlexAbleVar(_, _) | RigidAbleVar(_, _) => todo_abilities!("Not reachable yet"),
RecursionVar { structure, .. } => {
let structure_content = env.subs.get_content_without_compacting(structure);

View File

@ -1,9 +1,6 @@
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_evals_to;
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::expect_runtime_error_panic;
// #[cfg(feature = "gen-dev")]
// use crate::helpers::dev::assert_evals_to;
@ -16,7 +13,9 @@ use crate::helpers::with_larger_debug_stack;
#[allow(unused_imports)]
use indoc::indoc;
#[allow(unused_imports)]
use roc_std::{RocList, RocStr};
use roc_std::{RocList, RocResult, RocStr};
use core::convert::Infallible;
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
@ -257,6 +256,59 @@ fn list_sublist() {
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_map_try_ok() {
assert_evals_to!(
// No transformation
r#"
List.mapTry [1, 2, 3] \elem -> Ok elem
"#,
RocResult::ok(RocList::<i64>::from_slice(&[1, 2, 3])),
RocResult<RocList<i64>, ()>
);
assert_evals_to!(
// Transformation
r#"
List.mapTry [1, 2, 3] \num ->
str = Num.toStr (num * 2)
Ok "\(str)!"
"#,
RocResult::ok(RocList::<RocStr>::from_slice(&[
RocStr::from("2!"),
RocStr::from("4!"),
RocStr::from("6!"),
])),
RocResult<RocList<RocStr>, ()>
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_map_try_err() {
assert_evals_to!(
r#"
List.mapTry [1, 2, 3] \_ -> Err -1
"#,
RocResult::err(-1),
RocResult<RocList<Infallible>, i64>
);
assert_evals_to!(
// If any element returns Err, the whole thing returns Err
r#"
List.mapTry [1, 2, 3] \num ->
if num > 2 then
Err -1
else
Ok num
"#,
RocResult::err(-1),
RocResult<RocList<i64>, i64>
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_split() {
@ -2670,18 +2722,8 @@ fn list_any() {
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[should_panic(expected = r#"Roc failed with message: "UnresolvedTypeVar"#)]
fn list_any_empty_with_unknown_element_type() {
// Segfaults with invalid memory reference. Running this as a stand-alone
// Roc program, generates the following error message:
//
// Application crashed with message
// UnresolvedTypeVar compiler/mono/src/ir.rs line 3775
// Shutting down
//
// TODO: eventually we should insert the empty type for unresolved type
// variables, since that means they're unbound.
expect_runtime_error_panic!("List.any [] (\\_ -> True)");
assert_evals_to!("List.any [] (\\_ -> True)", false, bool);
}
#[test]
@ -2695,18 +2737,8 @@ fn list_all() {
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[should_panic(expected = r#"Roc failed with message: "UnresolvedTypeVar"#)]
fn list_all_empty_with_unknown_element_type() {
// Segfaults with invalid memory reference. Running this as a stand-alone
// Roc program, generates the following error message:
//
// Application crashed with message
// UnresolvedTypeVar compiler/mono/src/ir.rs line 3775
// Shutting down
//
// TODO: eventually we should insert the empty type for unresolved type
// variables, since that means they're unbound.
expect_runtime_error_panic!("List.all [] (\\_ -> True)");
assert_evals_to!("List.all [] (\\_ -> True)", true, bool);
}
#[test]
@ -2975,9 +3007,6 @@ fn call_function_in_empty_list() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
// TODO to be improved, if we can generate the void function type for the function in the list,
// this should succeed.
#[should_panic(expected = r#"Roc failed with message: "#)]
fn call_function_in_empty_list_unbound() {
assert_evals_to!(
indoc!(

View File

@ -2589,8 +2589,7 @@ fn module_thunk_is_function() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic(expected = "Roc failed with message: ")]
fn hit_unresolved_type_variable() {
fn pass_through_unresolved_type_variable() {
assert_evals_to!(
indoc!(
r#"

View File

@ -182,3 +182,9 @@ impl<T: FromWasm32Memory, U: FromWasm32Memory, V: FromWasm32Memory> FromWasm32Me
(t, u, v)
}
}
impl FromWasm32Memory for std::convert::Infallible {
fn decode(_memory_bytes: &[u8], _offset: u32) -> Self {
unreachable!()
}
}