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

Safe call operators ?. #2142

Open
wants to merge 12 commits into
base: master
from

Conversation

Projects
None yet
10 participants
@Gregoirevda
Copy link
Contributor

commented Aug 14, 2018

This PR adds support for ?., ?#, ?.+ and ?#+
Useful for accessing deeply nested properties.

Instead of writing

let userName = switch response {
  | None => None
  | Some(response) => switch response.user {
    | None => None
    | Some(user) => switch user.name {
      | None => None
      | Some(name) => Some(name)
    }
  }
};

You can write

let userName = response?.+user?.name;

Same goes for accessing Javascript objects with ?# and ?#+.

Note the + for record and Javascript objects access operators representing a bind.

?. represents a map:

type a = option({
  b: string
});

let res = switch a {
  | None => None
  | Some(a) => Some(a.b) 
};

?.+ represents a bind

type a = option({
  b: option(string)
});

let res = switch a {
  | None => None
  | Some(a) => a.b /* <-- Not elevated */
};

If the field is optional, use ?. otherwise use ?.+

Note on token associativity:
You would think ?. is left associative (a?.b)?.c, but since ?. is not runtime execution but sugar for a switch, it has to be right associative to construct itself from the deeply nested part to its parent.

Right associativity: (current)

switch a  {
  | None => None
  | Some(a) => 
    let b = Some(a.b);
    switch b {
      | None => None
      | Some(b) => Some(b.c)
    }
}

left associativity

switch(
switch a  {
  | None => None
  | Some(a) =>  Some(a.b)
}) {
    | None => None
    | Some(b) => Some(b.c)
}

TODO:

  • tests
  • Define bind infix operator syntax (not sure the + is appropriate)
  • print switch back into ?. for remft in reason_pprint_ast

Linked issue: #1928

@thangngoc89

This comment has been minimized.

Copy link
Contributor

commented Aug 14, 2018

I love this feature. I have a lot of nested switch statement like this when working with graphql.

@hcarty

This comment has been minimized.

Copy link
Contributor

commented Aug 14, 2018

This is specific to option values, correct? It would be nice for new syntax to have some means of broader type support, so that at least result could be handled.

@jaredly

This comment has been minimized.

Copy link
Contributor

commented Aug 14, 2018

I like the idea of optional-access operators, but I'm not super happy about the syntax :/ feels pretty obfuscating, hard to google, etc.
I'm also not sure that record access is the most common use case that we should be optimising for.

What if we instead extended _ anonymous functions to include attribute access? then your example would become:

open Option; // assuming it gives us map, bind
let userName = response->bind(_.user)->bind(_.name)

Which, though a little less terse, I think is more understandable / googleable / cmd+clickable for newcomers.

Also, in the context of the let!opt PR there's a nice unification, in that you'd be doing

let!opt userName = response->opt(_.user)->opt(_.name);
"Hello " ++ userName

it's all the same function you're calling.

@Gregoirevda

This comment has been minimized.

Copy link
Contributor Author

commented Aug 14, 2018

Most Javascript developers know Typescripts ?.. It feels pretty natural.
JS tc39 proposal for ?.. Kotlin and Swift uses them too.

I think the problem for newcomers are the bind operators ?.+ and ?#+.

@jaredly

This comment has been minimized.

Copy link
Contributor

commented Aug 14, 2018

But the idea of promoting attribute access at this syntax level is a very OO kind of a thing (which typescript, js, kotlin, and swift are), and all of those implementations require some amount of type-loose-shanigans.
I think it's notable that Rust (probably Reason's closest cousin) doesn't have a null-access operator.

@Gregoirevda

This comment has been minimized.

Copy link
Contributor Author

commented Aug 14, 2018

Note that you can do

let name = switch response {
 | None => None
 | Some({user: {name}}) => Some(name)
};

when it's a record, but not when it's a JS object. That's the original issue.

@jaredly

This comment has been minimized.

Copy link
Contributor

commented Aug 14, 2018

So with a javascript object we could similarly do

open Option; // assuming it gives us map, bind
let userName = response->bind(_##user)->bind(_##name)
// or, for if you want more clarity/verbosity
let userName = response->bind(response => response##user)->bind(user => user##name)

I think this gets what we need (freedom from endless switch statements) without adding complexity to the language

@alexeygolev

This comment has been minimized.

Copy link

commented Aug 14, 2018

@jaredly this is very similar to Scala's placeholder syntax which I miss very much

@cloudkite

This comment has been minimized.

Copy link

commented Aug 15, 2018

@cloudkite

This comment has been minimized.

Copy link

commented Aug 15, 2018

@Gregoirevda feels a little weird to have differentiate between bind and map, can the parser not detect and write correct statement according to the type?

@jaredly

This comment has been minimized.

Copy link
Contributor

commented Aug 15, 2018

@cloudkite Rust has an operator for propagating errors - a ? suffix that will early return with an error from the function.

can the parser not detect and write correct statement according to the type?

definitely not, unfortunately -- the parser has no knowledge of types -- the type inference phase runs well after refmt is done doing its work.

@Gregoirevda

This comment has been minimized.

Copy link
Contributor Author

commented Aug 16, 2018

@jaredly coming from Standard ML, OCaml has, among others, OO paradigm: Object, Classes and inheritence. Rust, even if coming from Standard ML, isn't OO. So, saying that ?. is only for OO languages like JS, isn't totaly correct I think.

About Kotlin and Swift, let s hope reason-react-native will be a success. Android and IOS devs will probably appreciate the ?. they're used too.

Even better than cmd+clickable, with 3 lines of code comment to explain what the function is doing, is good naming for functions.
?. seems like a perfect 'naming'.

@cloudkite unfortunately, the parsers just gets strings and tokens.

@cristianoc

This comment has been minimized.

Copy link
Contributor

commented Aug 30, 2018

Here's an experiment for auto-unwrapping options when accessing fields: cristianoc/ocaml#1

@jaredly

This comment has been minimized.

Copy link
Contributor

commented Aug 30, 2018

😮🤯👏👏👏 that's veryyy cool

@ostera

This comment has been minimized.

Copy link

commented Sep 11, 2018

Feels a bit misleading to auto-unwrap 🤔 what's the difficulty in learning about >>=, >>|, or <|> ?

let username = response >>= owner >>| name <|> "no_username_available";
"Hello " ++ username

Defining custom operators like above is already completely supported, has plenty of literature around it, has no significant impact on the syntax of the language, and can be even optimized away by the compiler (like Flambda does).

This is starting to smell a lot like the -> debate 😛

@Gregoirevda

This comment has been minimized.

Copy link
Contributor Author

commented Sep 11, 2018

Ideally, we should have record typing for Javascript objects.
Record typing already solved this, it's all about bsb here. So adding an infix operator to solve a bsb problem is not the solution.

I'll leave this open until some proposition is made in bsb

@jaredly

This comment has been minimized.

Copy link
Contributor

commented Oct 31, 2018

I made a ppx to try out this safe-call kind of thing (to relieve the pain of working with graphql results) in userland https://github.com/jaredly/get_in

@Gregoirevda

This comment has been minimized.

Copy link
Contributor Author

commented Oct 31, 2018

Cool you made a ppx for it!

@jaredly

This comment has been minimized.

Copy link
Contributor

commented Oct 31, 2018

The idea being that we can prove out the concept for a while, and then have a better idea of how much it is needed, etc. to decide whether to add it to the core syntax :)

@shinzui

This comment has been minimized.

Copy link

commented Oct 31, 2018

@Gregoirevda

coming from Standard ML, OCaml has, among others, OO paradigm: Object, Classes and inheritence.

Seasoned OCaml developers avoid the OO features of OCaml, some of them even wish they can remove it from the language. It was added mostly as a marketing ploy to compete with Java.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.