Looking for feedback on my Belt PR

Ok so it seems that the large majority is for orElse. I’ll update my PR later today.

I’m also wondering whether we should build out Option with some more functions from its typical typeclass implementations in other languages. I can think of a few other useful ones, but I’m not sure how easy it would be to express them in Rescript.

Looks like in Belt there’s implementations for Equality, Ordinal, Functor and Monad (and now Alternative with this PR). I’m thinking for Option in particular, it would be useful to also implement Semigroup and Monoid in Belt. I’ll chill on advocating for implementing Applicatives, because I can see how that usecase might be out of the scope of Belt, and you could always bring in another library if you wanted those (which I do, haha).

However, I can see a real usecase for having things like this in Belt (I’m making up names as I go):

open Belt.Option

// only works on monoids like string, list, and array
Some([1,2])->conjoin(Some([3,4])) // Some([1,2,3,4])
Some([1,2])->conjoin(None) // Some([1,2])
None->conjoin(Some([3,4]) // Some([3,4])

// maybe not the best name in the world
Some([1,2])->concat // [1,2]
None->concat // []

[Some(1), Some(2)]->sequence // Some([1,2])
[Some(1), None]->sequence // None

[Some(1), None, Some(2)]->catOption // [1,2]

// essentially like mapping, then filtering out failed operations
// which is something I find myself doing all the time in JS
["1", "blah", "2"]->mapOption(Belt.Int.fromString) // [1,2]

Like I said, I could imagine the average user reaching for things like these. I can sympathize with wanting to keep the stdlib lean, but these feel pretty standard and useful to me. Maybe it’s out of the scope of this discussion though.

1 Like

Is there a reasonable pattern for “extendind” belt modules? Usually when I do something like creating my own util functions, I’ll end up having code that goes array->Belt.Array.__->Js.Array2.__->Util.Array.__

There are two. For Option, my team wants to hide most of the Belt functions so we use open and manually redirect to the ones we want:

open Belt.Option

let isSome = isSome
let isNone = isNone
let value = (opt, ~default) => getWithDefault(opt, default)
// other custom functions

Which compiles to nice JS:

import * as Belt_Option from "rescript/lib/es6/belt_Option.js";

var isSome = Belt_Option.isSome;
var isNone = Belt_Option.isNone;
var value = Belt_Option.getWithDefault;

This style of export redirect seems to be supported by bundle tree shaking so the Belt function is used directly rather than via our variable.

For other modules such as Js.Dict we are happy with most of the functions and just want to override some of them, so we use include:

include Js.Dict

let map = (dict, ~f) => map((. v) => f(v), dict)

Which compiles to the same pattern:

import * as Js_dict from "rescript/lib/es6/js_dict.js";

var get = Js_dict.get;
var unsafeDeleteKey = Js_dict.unsafeDeleteKey;
// repeat the above for every other function we didn't override

function map(dict, f) {
  return Js_dict.map(Curry.__1(f), dict);
}
4 Likes

@benadamstyles I think it’s worth summarising the discussion, in particular the context.
It seems most people like the orElse name. It should be remarked, so everyone is aware, that this is a little inconsistent with the naming in the rest of the module. And this inconsistency was discussed.
It was also discussed that making changes to the existing names, to bring more inconsistency, is very difficult. For backwards compatibility.
Also, it was remarked that it’s easy to make little additions for your own use of the library. So this is about the basic defaults for everybody.

Putting this out there just in case people have further thoughts if they were not aware of the full context.

2 Likes