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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Map over nullability #48214

Open
giladwo opened this issue Jan 24, 2022 · 4 comments
Open

Map over nullability #48214

giladwo opened this issue Jan 24, 2022 · 4 comments
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core type-enhancement A request for a change that isn't a bug

Comments

@giladwo
Copy link

giladwo commented Jan 24, 2022

Hi all, it's my first first time opening an issue so I hope it goes well 馃憢
I'm not sure if this is the right place, because it's less of a new language feature and more of an improvement of the standard library.

The problem: ?. only supports methods (including extension methods)

For example, indexing into a Map<K, V> returns a V?.
We can always add an extension on V to have a nice m[key]?.foo(), but adding a lot of extensions just to enjoy ?. is inelegant and there's surely a better solution.

The proposed solution: a MappableNullable extension

Consider the following:

extension MapNullable<T> on T? {
    V? map<V>(V Function(T) f) => this == null ? null : f(this!);
}

This is a slightly more useful version of ?.:

// assuming a `doSomething` method:
x?.doSomething()  // still works
x.map((x) => x.doSomething())  // a little more cumbersome, no reason to use if there's `?.`

// assuming a `doSomething` free function:
x == null ? null : doSomething(x!)  // out of luck, can't use `?.`
x.map(doSomething)  // works

I'm proposing this addition to the standard library because it's applicable pretty much anywhere nullables are involved.
For instance, Kotlin has a few similar helpers. Especially, let can trivially be used as mapNullable.

Addendum:

Going a step further, here are a few more extensions that I'd be happy to have in the standard library.
These are useful abstractions, but arguably less widely applicable, being tailored to a very functional style.

// helpers for the following extensions
T Function() always<T>(final T result) => () => result;
T Function(I) always2<T, I>(final T result) => (final I _) => result;
T identity<T>(final T result) => result;

/// just like MapNullable, but handles both cases
/// slightly nicer than `x.mapNullable(f) ?? z` when z is a thunk, i.e. `x.mapNullable(f) ?? (() => ...)()`
extension FoldableNullable<T> on T? {
    V fold<V>(V Function(T) onSome, V Function() onNone) =>
        this == null ? onNone() : onSome(this!);
}

/// a nicer implementation given FoldableNullable
extension FunctorNullable<T> on T? {
    V? map<V>(V Function(T) f) => fold(f, always(null));
}

/// useful for chaining thunked defaults, i.e. `myMap[missingKey].mapNullable(happyPath).orElseDo(sadPath)`.
extension WithDefault<T> on T? {
    T orElseDo(T Function() f) => fold(identity, f);
    T orElse(T fallback) => orElseDo(always(fallback));
}

/// useful for validations, i.e. Flutter commonly expects validations to return a `String?` that indicates an error or lack thereof.
/// i.e. `validate(someInput).onNullDo(happyPath)` invokes `happyPath` only if the validation is successful (return error is `null`).
extension Invert<T> on T? {
    V? onNullDo<V>(V Function() f) => fold(always2(null), f);
    V? onNull<V>(V fallback) => onNullDo(always(fallback));
}

Related:

@lrhn
Copy link
Member

lrhn commented Jan 25, 2022

I'd honestly prefer a non-nullaware version of map:

extension Map<T> on T {
  V map<V>(V Function(T) action) => action(this);
}

Then you can still do e?.map(foo) if you want the null-awareness, and e.map(foo) if e isn't nullable (or if it is, and so is foo, I didn't make it <T extends Object>).

@lrhn lrhn transferred this issue from dart-lang/language Jan 25, 2022
@lrhn lrhn added area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core type-enhancement A request for a change that isn't a bug labels Jan 25, 2022
@giladwo
Copy link
Author

giladwo commented Jan 25, 2022

Perfectly fine by me (although I'm used to call this function let, which would also avoid clashing with existing map methods i.e. List's).

Any thoughts on onElseDo?

@lrhn
Copy link
Member

lrhn commented Jan 25, 2022

I like let better than map too.

If I understand it correctly, e.orElseDo(foo) is exactly the same as e ?? foo().
That makes myMap[missingKey].mapNullable(happyPath).orElseDo(sadPath) the same as myMap[missingKey]?.let(happyPath) ?? sadPath().

If so, I don't see the big improvement. I find the null control flow easier to read by following the ?s in the latter expression.

@giladwo
Copy link
Author

giladwo commented Jan 25, 2022

I like ?. and ?? too. They're much less obtrusive.

For me, the main (only?) disadvantage of them is that they're specific to nullity and can't deal with other kinds of side effects.
Perhaps I'm ruined by Haskell, but I don't like specializing for anything, especially not for side effects.

Here's to hoping we'll see let and friends in the standard soon 馃帀 .
Thanks for the quick replies!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests

2 participants