Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change the approach to Option and Result iteration #185

Merged
merged 2 commits into from
Jan 15, 2025

Conversation

jstasiak
Copy link
Collaborator

Previously iterating Some and Ok objects depended on whether the underlying type T was iterable or not.

If it was the results of the iteration were obtained by iterating the underlying value. There could be an arbitrary number of results.

For example this was the case before (using Some to demonstrate the idea, the same applies to Ok):

// Some<number>, no results because 1 is not iterable
for (const e of Some(1)) { ... }
// Some<number[]>, two results produced
for (const e of Some([1, 2])) { ... }
// Some<number[]>, no results produced because the underlying value
// contains no items
for (const e of Some([]) as Option<number[]>) { ... }

This mode of operation never quite worked for me because:

  1. It depended on the underlying value being iterable and didn't provide any value in case of numbers, strings and other non-iterable values.
  2. It made it impossible to use iteration of Option and Result values to conditionally run some code only when dealing with Some and Ok instances.

This patch tackles both of these issues at once (they're connected).

The change brings the iteration behavior in line with how Rust's Option and Result do things[1] (zero-or-one iteration resuls, never multiple results).

If one needs the old behavior it can be achieved with a little bit of additional code, it's not the case in the opposite direction. For example:

const ids: Option<number[]> = ...

// Previously:
for (const id of ids) { ... }
// Now (one of potential solutions):
for (const id of ids.unwrapOr([])) { ... }

[1] https://doc.rust-lang.org/std/option/enum.Option.html#method.iter

Previously iterating Some and Ok objects depended on whether the
underlying type T was iterable or not.

If it was the results of the iteration were obtained by iterating the
underlying value. There could be an arbitrary number of results.

For example this was the case *before* (using Some to demonstrate the
idea, the same applies to Ok):

    // Some<number>, no results because 1 is not iterable
    for (const e of Some(1)) { ... }
    // Some<number[]>, two results produced
    for (const e of Some([1, 2])) { ... }
    // Some<number[]>, no results produced because the underlying value
    // contains no items
    for (const e of Some([]) as Option<number[]>) { ... }

This mode of operation never quite worked for me because:

1. It depended on the underlying value being iterable and didn't
   provide any value in case of numbers, strings and other non-iterable
   values.
2. It made it impossible to use iteration of Option and Result values
   to conditionally run some code only when dealing with Some and Ok
   instances.

This patch tackles both of these issues at once (they're connected).

The change brings the iteration behavior in line with how Rust's Option
and Result do things[1] (zero-or-one iteration resuls, never multiple
results).

If one needs the old behavior it can be achieved with a little bit of
additional code, it's not the case in the opposite direction. For
example:

    const ids: Option<number[]> = ...

    // Previously:
    for (const id of ids) { ... }
    // Now (one of potential solutions):
    for (const id of ids.unwrapOr([])) { ... }

[1] https://doc.rust-lang.org/std/option/enum.Option.html#method.iter
@jstasiak jstasiak changed the title Change the way we do Option and Result iteration Change the approach to Option and Result iteration Jan 13, 2025
test/ok.test.ts Outdated Show resolved Hide resolved
const values = Array.from(Ok('hello'));
expect(Array.from(Ok('hello'))).toEqual(['hello']);
expect(Array.from(Ok([1, 2, 3]))).toEqual([[1, 2, 3]]);
expect(Array.from(Ok(1))).toEqual([1]);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we have some tests for Err here as well?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already present in err.test.ts as:

test('iterable', () => {
    for (const item of Err([123])) {
        expect_never(item, true);
        throw new Error('Unreachable, Err@@iterator should emit no value and return');
    }
});

One of these days I'll merge these test files, the split always confuses me.

CHANGELOG.md Show resolved Hide resolved
Comment on lines +13 to +16
const o: Option<number[]> = Some([1, 2, 3])
const rs = Array.from(o)
// Previously: rs was [1, 2, 3]
// Now: rs equals [[1, 2, 3]]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example is super clear 👍

@jstasiak jstasiak merged commit c770ac8 into master Jan 15, 2025
2 checks passed
@jstasiak jstasiak deleted the iteration-approach branch January 15, 2025 12:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants