Looking for feedback on my Belt PR

Hi! I just made a PR to Belt to add a new Option function. I was asked to get some wider input, which is a good idea, so I’d appreciate your thoughts on 2 things – is this a good addition? And which naming do you prefer? To save you navigating, I’ll paste the PR description below, followed by a naming poll.

Edit: here’s what you’d be able to do if the PR gets merged:

let value = option
  ->Option.orElse(alternativeOption)
  ->Option.getWithDefault("string")

Add new function to Option: withDefault

We find ourselves doing the following kind of thing quite often:

let opt = switch option {
  | Some(_) as some => some
  | None => alternativeOption
}

let value = opt->Option.getWithDefault("string")

Option types in other languages already support using alternative options in the standard lib:

In terms of naming, I followed the convention from Scala: getOrElse is to orElse as Belt.Option.getWithDefault is to Belt.Option.withDefault. An alternative would be to use orElse, that might be better than using the word “default” for an optional value.


  • Option.withDefault
  • Option.orElse
  • Option.or
  • Option.<something else!>

0 voters

1 Like

value in OCaml: OCaml library : Option

I think ocaml’s value Is the equivalent of rescript’s getWithDefault? I don’t think ocaml has an equivalent of the function I’ve added in the MR, which probably explains why it wasn’t added to Belt originally.

OK, so maybe I’m stupid, but how is this different from getWithDefault?

1 Like

Oh wait, the code you’ve listed is not the new idiom your PR would enable? :slight_smile: Can you list that, please.

I aslo didn’t realise at first that this is different from getWithDefault. “orElse” would help avoid this confusion.

3 Likes

See the |? operator from Batteries: Batteries user guide : BatPervasives

Ok I updated the original post.

|? is the same as getWithDefault.

What I’m proposing is different:

let getWithDefault: (option<'a>, 'a) => 'a
let orElse: (option<'a>, option<'a>) => option<'a>

I called this selfOr in my local stdlib. I also made the alternate value lazy, with a matching selfOrStrict that just takes a plain option; in the project I work on alternate values are rarely cheap to calculate. But that’s personal preference :joy:

I voted for the rust style or, for the record.

1 Like

So, orElse is essentially <|> from Haskell? I’m 100% in favor of having it. I was recently caught off guard that it wasn’t already in there.

I think the naming is fine. I actually want to change getWithDefault more. It’s kind of a verbose function name for something that is inevitably going to be used all the time.

1 Like

I like orElse though I agree that it would be nice if getWithDefault followed a similar convention. Something like orElse() and orElseValue()

I agree, although we can’t really change it now, I use the OCaml style for this in my stdlib because I really like named arguments. The PR example, in my codebase, becomes:

let value = option
  ->Option.selfOr(alternative)
  ->Option.value(~default="string")

Does this use-case also relate to the Maybe/Option monad, when you string along multiple option values? Or am I totally wrong? Dunno if ReScript supports the let* notation.

I’d prefer to have something like flatMapNone instead

let flatMapNone: (option<'a>, () => option<'b>) => option<' b>

Because often the “else” option should be computed in some way, so it’s good to do it only when the first option is None

2 Likes

we have it as first for application in fold.

ReScript already has an easy concept for thunks, called lazy. This is why my selfOr method is lazy by default - and I have one for the unwrap case as well:

let selfOr: (option<'a>, Lazy.t<option<'a>>) => option<'a>
let orLazy: (option<'a>, Lazy.t<'a>) => 'a

Lazy is a fun concept, code is written as if the value is returned immediately but computation is suspended (it compiles to a function in JS so it’s almost exactly equivalent to your proposal).

let value = option
  ->Option.selfOr(lazy(someReallyExpensiveOperation())

For another comparison, this is called first_some in Jane Street’s Base library.

Not commenting one way or another on the name, but if you are interested in some uses “in the wild”, you can check out a bunch of examples in this sherlocode search. (Examples in OCaml, but you will get the idea.)

2 Likes

I see two problems with lazy for this usecase.

  1. It’s confusing for beginners
  2. The code with lazy will work not as we used to with map, flatMap. If it’s called the second time it will return the first result, without recomputing it, even though it could have changed
2 Likes

Wow, didn’t know this is possible.

I agree with @DZakh 's points though, and would also add:

  1. lazy(x) is same amount of code than () => x
  2. Lazy.force(val) is more code than val()
  3. Generated JS is more complicated, and even adds a require require("./stdlib/camlinternalLazy.js")

But lazy can be useful if this is exactly what you want.

Slightly off-topic, as not in “what goes in belt”. One could consider overloading || which just happens to already have the correct semantics based on the runtime representation of optionals.

Plus built-in laziness.