Skip to content

Option additions #57

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
- Add `panic`/`Error.panic`. https://github.com/rescript-association/rescript-core/pull/72
- The globally available `null` value now originates from `Nullable` and not `Null`, just like the globally available `undefined` value does. https://github.com/rescript-association/rescript-core/pull/88
- Add `Int.range` and `Int.rangeWithOptions`, https://github.com/rescript-association/rescript-core/pull/52
- Add `flat`, `expect`, and `or` to `Option`, deprecate `orElse`. https://github.com/rescript-association/rescript-core/pull/57

### Documentation

Expand Down
37 changes: 29 additions & 8 deletions src/Core__Option.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

import * as Curry from "rescript/lib/es6/curry.js";
import * as Caml_option from "rescript/lib/es6/caml_option.js";
import * as Core__Error from "./Core__Error.mjs";

function flat(opt) {
if (opt !== undefined) {
return Caml_option.valFromOption(opt);
}

}

function filter(opt, p) {
var p$1 = Curry.__1(p);
Expand All @@ -19,16 +27,24 @@ function forEach(opt, f) {

}

function getExn(x) {
if (x !== undefined) {
return Caml_option.valFromOption(x);
function getExn(opt) {
if (opt !== undefined) {
return Caml_option.valFromOption(opt);
}
throw {
RE_EXN_ID: "Not_found",
Error: new Error()
};
}

function expect(opt, message) {
if (opt !== undefined) {
return Caml_option.valFromOption(opt);
} else {
return Core__Error.panic(message);
}
}

function mapWithDefault(opt, $$default, f) {
var f$1 = Curry.__1(f);
if (opt !== undefined) {
Expand Down Expand Up @@ -62,20 +78,20 @@ function getWithDefault(opt, $$default) {
}
}

function orElse(opt, other) {
function or(opt, other) {
if (opt !== undefined) {
return opt;
} else {
return other;
}
}

function isSome(x) {
return x !== undefined;
function isSome(opt) {
return opt !== undefined;
}

function isNone(x) {
return x === undefined;
function isNone(opt) {
return opt === undefined;
}

function eq(a, b, f) {
Expand Down Expand Up @@ -106,14 +122,19 @@ function cmp(a, b, f) {
}
}

var orElse = or;

export {
flat ,
filter ,
forEach ,
getExn ,
expect ,
mapWithDefault ,
map ,
flatMap ,
getWithDefault ,
or ,
orElse ,
isSome ,
isNone ,
Expand Down
54 changes: 32 additions & 22 deletions src/Core__Option.res
Original file line number Diff line number Diff line change
Expand Up @@ -22,73 +22,83 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */

let flat = opt =>
switch opt {
| Some(v) => v
| None => None
}

let filterU = (opt, p) =>
switch opt {
| Some(x) as some if p(. x) => some
| Some(value) as some if p(. value) => some
| _ => None
}

let filter = (opt, p) => filterU(opt, (. x) => p(x))
let filter = (opt, p) => filterU(opt, (. value) => p(value))

let forEachU = (opt, f) =>
switch opt {
| Some(x) => f(. x)
| Some(value) => f(. value)
| None => ()
}

let forEach = (opt, f) => forEachU(opt, (. x) => f(x))
let forEach = (opt, f) => forEachU(opt, (. value) => f(value))

let getExn = x =>
switch x {
| Some(x) => x
let getExn = opt =>
switch opt {
| Some(value) => value
| None => raise(Not_found)
}

let expect = (opt, message) =>
Copy link
Member

Choose a reason for hiding this comment

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

Not sure if we shouldn't rather leave the implementation of such a function to e.g. testing frameworks (which might also want to raise a different exception).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This isn't for testing. You should consider using this every time you reach for getExn because it gives you a better, easier to track down error message when the assumption that lead you to using it inevitably fails in production 🙃

Copy link
Collaborator

Choose a reason for hiding this comment

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

One thing that comes to mind here - do we need an additional alternative where you can pass your own exception? I recall people in the community talking about adding that themselves.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What's the use case for that? To have a distinct exception that they can catch?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah, exactly. But let's ignore it for now, feels peripheral.

Copy link
Member

Choose a reason for hiding this comment

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

Ok, right, so there is definitely demand in the community for something like this.

And what do you think about the naming? Usually we have exn in the method name when the function might raise. That wouldn't be the case with expect. Maybe it should be getExnWithMessage?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that would ensure that absolutely no-one would pick this over getExn, unfortunately 😉

Copy link
Contributor Author

Choose a reason for hiding this comment

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

expectExn could be an alternative. Though I don't think the convention is that functions that raise should always use Exn, but rather that it signifies a variant of a function that distinguishes itself by raising. Similar to how not all functions that can return an option uses Opt.

Is there not an annotation that can be put on functions that raise? I thought reanalyze utilized that, but I can't find it now.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Naming this one is hard for sure... I'm trying to come up with a good alternative as well. I like expect because I've used it a lot in Rust, but I'm not sure it'd feel very familiar to people who haven't used Rust. I'm ok with getExnWithMessage, but I understand if it feels too verbose. So let's maybe think a round or two more about what could be a good alternative name, before we decide.

@glennsl correct, it's @raises: https://rescript-lang.org/syntax-lookup#raises-decorator
We should eventually work our way through all the bindings and ensure all of them have @raises where appropriate. That'll be a lot of work though, so probably wise to do it little by little, over time. It's also less important than the general documentation is right now.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I logged an issue for @raises.

Still haven't figured out a better name than expect or getExnWithMessage. Anyone else got something? Otherwise we'll need to figure out the best choice between those two.

switch opt {
| Some(value) => value
| None => Core__Error.panic(message)
}

external getUnsafe: option<'a> => 'a = "%identity"

let mapWithDefaultU = (opt, default, f) =>
switch opt {
| Some(x) => f(. x)
| Some(value) => f(. value)
| None => default
}

let mapWithDefault = (opt, default, f) => mapWithDefaultU(opt, default, (. x) => f(x))
let mapWithDefault = (opt, default, f) => mapWithDefaultU(opt, default, (. value) => f(value))

let mapU = (opt, f) =>
switch opt {
| Some(x) => Some(f(. x))
| Some(value) => Some(f(. value))
| None => None
}

let map = (opt, f) => mapU(opt, (. x) => f(x))
let map = (opt, f) => mapU(opt, (. value) => f(value))

let flatMapU = (opt, f) =>
switch opt {
| Some(x) => f(. x)
| Some(value) => f(. value)
| None => None
}

let flatMap = (opt, f) => flatMapU(opt, (. x) => f(x))
let flatMap = (opt, f) => flatMapU(opt, (. value) => f(value))

let getWithDefault = (opt, default) =>
switch opt {
| Some(x) => x
| Some(value) => value
| None => default
}

let orElse = (opt, other) =>
let or = (opt, other) =>
switch opt {
| Some(_) as some => some
| Some(_) => opt
| None => other
}

let isSome = x =>
switch x {
| Some(_) => true
| None => false
}
let orElse = or

let isSome = opt => opt !== None

let isNone = x => x == None
let isNone = opt => opt == None

let eqU = (a, b, f) =>
switch a {
Expand Down
48 changes: 44 additions & 4 deletions src/Core__Option.resi
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ let someString: option<string> = Some("hello")
```
*/

/**
`flat(value)` flattens a nested `option` value to a single level.

## Examples

```rescript
Option.flat(Some(Some(10))) // Some(10)
Option.flat(Some(None)) // None
```
*/
let flat: option<option<'a>> => option<'a>

/**
`filter(opt, f)` applies `f` to `opt`, if `f` returns `true`, then it returns `Some(value)`, otherwise returns `None`.

Expand Down Expand Up @@ -66,7 +78,7 @@ Option.forEach(None, x => Console.log(x)) // returns ()
let forEach: (option<'a>, 'a => unit) => unit

/**
`getExn(opt)` returns `value` if `opt` is `Some(value)`, otherwise raises an exception.
`getExn(opt)` returns `value` if `opt` is `Some(value)`, raises `Not_found` if `None`.

```rescript
Option.getExn(Some(3)) // 3
Expand All @@ -75,10 +87,24 @@ Option.getExn(None) /* Raises an Error */

## Exceptions

- Raises an error if `opt` is `None`
- Raises `Not_found` if `opt` is `None`
*/
let getExn: option<'a> => 'a

/**
`expect(opt, message)` returns `value` if `Some(value)`, raises a `Failure` expection with the given message if `None`.

```rescript
Option.expect(Some(3), "should not be None") // 3
Option.expect(None, "should not be None") // Raises `Failure("should not be None")`
```

## Exceptions

- Panics if `opt` is `None`
*/
let expect: (option<'a>, string) => 'a

/**
`getUnsafe(value)` returns `value`.

Expand All @@ -89,9 +115,9 @@ Option.getUnsafe(Some(3)) == 3
Option.getUnsafe(None) // Raises an error
```

## Exceptions
## Important

- This is an unsafe operation, it assumes `value` is neither `None` nor `Some(None(...)))`
This is an unsafe operation, it assumes `value` is neither `None`, `Some(None))`, `Some(Some(None))` etc.
*/
external getUnsafe: option<'a> => 'a = "%identity"

Expand Down Expand Up @@ -160,6 +186,19 @@ None->greet // "Greetings Anonymous"
*/
let getWithDefault: (option<'a>, 'a) => 'a

/**
`or(opt1, opt2)` returns `opt2` if `opt1` is `None`, otherwise `opt1`.

## Examples

```rescript
Option.or(Some(1812), Some(1066)) == Some(1812)
Option.or(None, Some(1066) == Some(1066)
Option.or(None, None) == None
```
*/
let or: (option<'a>, option<'a>) => option<'a>

/**
`orElse(opt1, opt2)` returns `opt2` if `opt1` is `None`, otherwise `opt1`.

Expand All @@ -171,6 +210,7 @@ Option.orElse(None, Some(1066) == Some(1066)
Option.orElse(None, None) == None
```
*/
@deprecated("Use `or` instead")
let orElse: (option<'a>, option<'a>) => option<'a>

/**
Expand Down