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

Add option module to Belt #2622

Merged
merged 11 commits into from Mar 16, 2018
Merged

Add option module to Belt #2622

merged 11 commits into from Mar 16, 2018

Conversation

arnarthor
Copy link
Contributor

@arnarthor arnarthor commented Mar 14, 2018

This adds the following utility functions for the option data type to belt.

val getExn : 'a option -> 'a
val mapWithDefaultU : 'a option -> 'b -> ('a -> 'b [@bs]) -> 'b
val mapWithDefault : 'a option -> 'b -> ('a -> 'b) -> 'b
val mapU : 'a option -> ('a -> 'b [@bs]) -> 'b option
val map : 'a option -> ('a -> 'b) -> 'b option
val flatMapU : 'a option -> ('a -> 'b option [@bs]) -> 'b option
val flatMap : 'a option -> ('a -> 'b option) -> 'b option
val getWithDefault : 'a option -> 'a -> 'a
val isSome : 'a option -> bool
val isNone : 'a option -> bool
val eqU : 'a option -> 'b option -> ('a -> 'b -> bool [@bs]) -> bool
val eq : 'a option -> 'b option -> ('a -> 'b -> bool) -> bool
val cmpU : 'a option -> 'b option -> ('a -> 'b -> int [@bs]) -> int
val cmp : 'a option -> 'b option -> ('a -> 'b -> int) -> int

Copy link
Member

@chenglou chenglou left a comment

Choose a reason for hiding this comment

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

Thanks! Left some comments

@@ -0,0 +1,27 @@
let getExn = function
| Some x -> x
| None -> assert false
Copy link
Member

Choose a reason for hiding this comment

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

I believe @bobzhang uses [%assert "getExn"] for some reason

| Some x -> x
| None -> assert false

let fold opt default f = match opt with
Copy link
Member

Choose a reason for hiding this comment

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

The name will need some thinking

Copy link
Contributor Author

Choose a reason for hiding this comment

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

People from the JS world probably know this as reduce or something along those lines at least for Lists/Arrays. We use fold internally because we're used to it from Scala/Haskell.

| Some x -> f x
| None -> default

let map opt f = match opt with
Copy link
Member

Choose a reason for hiding this comment

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

I think we'd also expose some mapU like other modules for consistency. But those can wait after this PR

| Some x -> Some (f x)
| None -> None

let flatMap opt f = match opt with
Copy link
Member

Choose a reason for hiding this comment

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

Same with fold regarding naming

| Some x -> x
| None -> default

let exists = function
Copy link
Member

Choose a reason for hiding this comment

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

Belt.List uses has. cc @bobzhang another thing we should unify around. has vs contains vs exists. First one is easier for non-english speakers

Copy link
Member

Choose a reason for hiding this comment

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

Actually, not sure why Belt.List.has exists

| Some _ -> true
| None -> false

let empty = function
Copy link
Member

Choose a reason for hiding this comment

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

This should be isEmpty

@arnarthor
Copy link
Contributor Author

I've addressed most of your comments @chenglou. I haven't changed the names for fold and flatMap.

Added uncurried versions of all the functions where that applies.

val flatMapU : 'a option -> ('a -> 'b option [@bs]) -> 'b option
val flatMap : 'a option -> ('a -> 'b option) -> 'b option
val getOrElse : 'a option -> 'a -> 'a
val has : 'a option -> bool
Copy link
Member

Choose a reason for hiding this comment

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

has is redundant?

val map : 'a option -> ('a -> 'b) -> 'b option
val flatMapU : 'a option -> ('a -> 'b option [@bs]) -> 'b option
val flatMap : 'a option -> ('a -> 'b option) -> 'b option
val getOrElse : 'a option -> 'a -> 'a
Copy link
Member

Choose a reason for hiding this comment

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

getWithDefault?

val mapU : 'a option -> ('a -> 'b [@bs]) -> 'b option
val map : 'a option -> ('a -> 'b) -> 'b option
val flatMapU : 'a option -> ('a -> 'b option [@bs]) -> 'b option
val flatMap : 'a option -> ('a -> 'b option) -> 'b option
Copy link
Member

Choose a reason for hiding this comment

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

bind or andThen?

Choose a reason for hiding this comment

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

Flatmap. It makes much more sense when you need to explain it to newcommers


val getExn : 'a option -> 'a
val foldU : 'a option -> 'b -> ('a -> 'b [@bs]) -> 'b
val fold : 'a option -> 'b -> ('a -> 'b) -> 'b
Copy link
Member

Choose a reason for hiding this comment

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

is fold common enough to be included?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We at least use it pretty often internally when dealing with a CLI tool we're writing. It's nice to have it to set defaults for missing arguments

Copy link
Member

Choose a reason for hiding this comment

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

this name does not sound meaningful to me, mapWithDefault looks better?

val flatMap : 'a option -> ('a -> 'b option) -> 'b option
val getOrElse : 'a option -> 'a -> 'a
val has : 'a option -> bool
val isEmpty : 'a option -> bool
Copy link
Member

Choose a reason for hiding this comment

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

I kinda feel x = None looks shorter than isEmpty and more efficient.

Some missed functionalities, eq, cmp?

@mchaver
Copy link
Contributor

mchaver commented Mar 14, 2018

@arnarthor @bobzhang How about a catSomes function

let catSomes (type a) (mas: (a option) list) :a list =
  List.fold_left(fun acc x ->
      match x with
      | None -> acc
      | Some a -> List.append acc [a]
    ) [] mas

and an array version

let arrayCatSomes (type a) (mas: (a option) array) :a array =
  Array.fold_left(fun acc x ->
      match x with
      | None -> acc
      | Some a -> Array.append acc [a]
    ) [||] mas

@arnarthor
Copy link
Contributor Author

I don't really see a usecase for it to be honest. This is just a specialized version of something like keep/filter for both Lists and Arrays right?

@mchaver
Copy link
Contributor

mchaver commented Mar 14, 2018

@arnarthor It's useful if you have collections of computations that can potentially fail, then you get the successful ones. It's a common function in the stdlibs of other functional programming languages like Haskell, Scala, Purescript, etc. catMaybes in those languages.

| None -> default

let has = function
| Some _ -> true
Copy link
Contributor

Choose a reason for hiding this comment

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

How about naming these isSome and isNone like Rust? I think they convey what the function actually does better than has/isEmpty.

Choose a reason for hiding this comment

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

agreed. In addition to isSome isNone:

Option.unwrap(Some(A)) vs Option.get(Some(A))
Option.unwrapOr(Some(A), B) vs Option.getOrElse(Some(A), B) vs Option.getWithDefault(Some(A), B)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

isSome and isNone are nice, but @bobzhang pointed out that x == None is more performant so they actually might not be needed.

I'd say that we should keep getExn and getWithDefault as is for consistency with other Belt modules which have get, getExn, getBy etc.

Choose a reason for hiding this comment

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

I agree with suffix consistency there but Option.get doesn't communicate to me that we are unwrapping / moving / extracting the value contained in the Option. unwrapExn unwrapWithDefault imo are more clear, but I don't feel super strongly about it

@arnarthor
Copy link
Contributor Author

@bobzhang I've added cmp and eq functions. I haven't renamed flatMap yet since we there isn't really a clear decisions on what should be used instead. I personally feel like flatMap is the most descriptive in this case, but I really don't mind if it's flatMap or bind. I don't like andThen in this case since it's not really descriptive for options.

I also kept has and isEmpty there but renamed them to isSome and isNone. I know they're redundant since you could use x === None but I feel like having these verbose functions could help newcomers read through code.

Anyways feel free to leave some more comments if you would like me to change something 👍

@bobzhang
Copy link
Member

Lets see if we can make it for release, see my comments about the naming of fold, is fold a good name for that function

@chenglou
Copy link
Member

Yeah I think mapWithDefault is a better name for fold too

@cullophid
Copy link

Since List already has map and flatten it would kinda make sense to call the combination flatMap...

@arnarthor
Copy link
Contributor Author

Done @bobzhang. Think it's ready for merge 👍

@arnarthor
Copy link
Contributor Author

I can't run a rebuild on travis, the instance stalled which caused the error.

@chenglou
Copy link
Member

Restarted for you. Related: #2564

@chenglou
Copy link
Member

I'm iffy on the name flatMap/bind/andThen, since it causes so much drama. Maybe we should leave it out for one version?

@bobzhang bobzhang merged commit 799b561 into rescript-lang:master Mar 16, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants