diff --git a/src/Core__Nullable.mjs b/src/Core__Nullable.mjs index edfbe37e..318bef24 100644 --- a/src/Core__Nullable.mjs +++ b/src/Core__Nullable.mjs @@ -7,8 +7,9 @@ import * as Core__Option from "./Core__Option.mjs"; function fromOption(option) { if (option !== undefined) { return Caml_option.valFromOption(option); + } else { + return null; } - } function equal(a, b, eq) { diff --git a/src/Core__Nullable.res b/src/Core__Nullable.res index a639671d..85217002 100644 --- a/src/Core__Nullable.res +++ b/src/Core__Nullable.res @@ -2,8 +2,6 @@ type t<'a> = Js.Nullable.t<'a> external null: t<'a> = "#null" -external undefined: t<'a> = "#undefined" - external make: 'a => t<'a> = "%identity" external toOption: t<'a> => option<'a> = "#nullable_to_opt" @@ -11,7 +9,7 @@ external toOption: t<'a> => option<'a> = "#nullable_to_opt" let fromOption: option<'a> => t<'a> = option => switch option { | Some(x) => make(x) - | None => undefined + | None => null } let equal = (a, b, eq) => Core__Option.equal(a->toOption, b->toOption, eq) diff --git a/src/Core__Nullable.resi b/src/Core__Nullable.resi index f303ca73..af53755a 100644 --- a/src/Core__Nullable.resi +++ b/src/Core__Nullable.resi @@ -1,57 +1,79 @@ /*** + Functions for handling nullable values. + + Primarily useful when interoping with JavaScript when you don't know whether you'll get a value, `null` or `undefined`. + */ /** + Type representing a nullable value. + A nullable value can be the value `'a`, `null` or `undefined`. + */ type t<'a> = Js.Nullable.t<'a> /** + The value `null`. + + See [`null`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/null) on MDN. + + ## Examples + ```rescript + Console.log(Nullable.null) // Logs `null` to the console. + ``` + */ external null: t<'a> = "#null" /** -The value `undefined`. - -See [`undefined`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/undefined) on MDN. -## Examples -```rescript -Console.log(undefined) // Logs `undefined` to the console. -``` -*/ -external undefined: t<'a> = "#undefined" - -/** Creates a new nullable value from the provided value. + This means the compiler will enforce null checks for the new value. + + ## Examples + ```rescript + let myStr = "Hello" + let asNullable = myStr->Nullable.make + + // Can't do the below because we're now forced to check for nullability + // myStr == asNullable + + // Need to do this + switch asNullable->Nullable.toOption { + | Some(value) if value == myStr => Console.log("Yay, values matched!") + | _ => Console.log("Values did not match.") + } + ``` + */ external make: 'a => t<'a> = "%identity" @@ -60,48 +82,85 @@ let equal: (t<'a>, t<'b>, ('a, 'b) => bool) => bool let compare: (t<'a>, t<'b>, ('a, 'b) => Core__Ordering.t) => Core__Ordering.t /** + Converts a nullable value into an option, so it can be pattern matched on. + Will convert both `null` and `undefined` to `None`, and a present value to `Some(value)`. + + ## Examples + ```rescript + let nullableString = Nullable.make("Hello") + + switch nullableString->Nullable.toOption { + | Some(str) => Console.log2("Got string:", str) + | None => Console.log("Didn't have a value.") + } + ``` + */ external toOption: t<'a> => option<'a> = "#nullable_to_opt" /** + Turns an `option` into a `Nullable.t`. + + ## Examples + ```rescript + let optString = Some("Hello") + let asNullable = optString->Nullable.fromOption // Nullable.t + ``` + */ let fromOption: option<'a> => t<'a> /** + `getOr(value, default)` returns `value` if not `null` or `undefined`, + otherwise return `default`. + + ## Examples + + ```rescript + Nullable.getOr(Nullable.null, "Banana") // Banana + Nullable.getOr(Nulalble.make("Apple"), "Banana") // Apple + + let greet = (firstName: option) => + "Greetings " ++ firstName->Nullable.getOr("Anonymous") + + Nullable.make("Jane")->greet // "Greetings Jane" + Nullable.null->greet // "Greetings Anonymous" + ``` + */ let getOr: (t<'a>, 'a) => 'a @@ -109,75 +168,133 @@ let getOr: (t<'a>, 'a) => 'a let getWithDefault: (t<'a>, 'a) => 'a /** + `getExn(value)` raises an exception if `null` or `undefined`, otherwise returns the value. + + ```rescript + Nullable.getExn(Nullable.make(3)) // 3 + Nullable.getExn(Nullable.null) /* Raises an Error */ + ``` + + ## Exceptions + + - Raises `Invalid_argument` if `value` is `null` or `undefined` + */ let getExn: t<'a> => 'a /** + `getUnsafe(value)` returns `value`. + + ## Examples + + ```rescript + Nullable.getUnsafe(Nullable.make(3)) == 3 + Nullable.getUnsafe(Nullable.null) // Raises an error + ``` + + ## Important + + - This is an unsafe operation, it assumes `value` is not `null` or `undefined`. + */ external getUnsafe: t<'a> => 'a = "%identity" /** + `forEach(value, f)` call `f` on `value`. if `value` is not `null` or `undefined`, + then if calls `f`, otherwise returns `unit`. + + ## Examples + + ```rescript + Nullable.forEach(Nullable.make("thing"), x => Console.log(x)) // logs "thing" + Nullable.forEach(Nullable.null, x => Console.log(x)) // returns () + Nullable.forEach(undefined, x => Console.log(x)) // returns () + ``` + */ let forEach: (t<'a>, 'a => unit) => unit /** + `map(value, f)` returns `f(value)` if `value` is not `null` or `undefined`, + otherwise returns `value` unchanged. + + ## Examples + + ```rescript + Nullable.map(Nullable.make(3), x => x * x) // Nullable.make(9) + Nullable.map(undefined, x => x * x) // undefined + ``` + */ let map: (t<'a>, 'a => 'b) => t<'b> /** + `mapOr(value, default, f)` returns `f(value)` if `value` is not `null` + or `undefined`, otherwise returns `default`. + + ## Examples + + ```rescript + let someValue = Nullable.make(3) + someValue->Nullable.mapOr(0, x => x + 5) // 8 + + let noneValue = Nullable.null + noneValue->Nullable.mapOr(0, x => x + 5) // 0 + ``` + */ let mapOr: (t<'a>, 'b, 'a => 'b) => 'b @@ -185,22 +302,40 @@ let mapOr: (t<'a>, 'b, 'a => 'b) => 'b let mapWithDefault: (t<'a>, 'b, 'a => 'b) => 'b /** + `flatMap(value, f)` returns `f(value)` if `value` is not `null` or `undefined`, + otherwise returns `value` unchanged. + + ## Examples + + ```rescript + let addIfAboveOne = value => + if (value > 1) { + Nullable.make(value + 1) + } else { + Nullable.null + } + + Nullable.flatMap(Nullable.make(2), addIfAboveOne) // Nullable.make(3) + Nullable.flatMap(Nullable.make(-4), addIfAboveOne) // undefined + Nullable.flatMap(Nullable.null, addIfAboveOne) // undefined + ``` + */ let flatMap: (t<'a>, 'a => t<'b>) => t<'b> diff --git a/test/Test.mjs b/test/Test.mjs index dac61739..b0223b89 100644 --- a/test/Test.mjs +++ b/test/Test.mjs @@ -39,7 +39,7 @@ function run(loc, left, comparator, right) { }, { highlightCode: true }); - var errorMessage = "\n \u001b[31mTest Failure!\n \u001b[36m" + file + "\u001b[0m:\u001b[2m" + String(line) + "\n" + codeFrame + "\n \u001b[39mLeft: \u001b[31m" + left$1 + "\n \u001b[39mRight: \u001b[31m" + right$1 + "\u001b[0m\n"; + var errorMessage = "\r\n \u001b[31mTest Failure!\r\n \u001b[36m" + file + "\u001b[0m:\u001b[2m" + String(line) + "\r\n" + codeFrame + "\r\n \u001b[39mLeft: \u001b[31m" + left$1 + "\r\n \u001b[39mRight: \u001b[31m" + right$1 + "\u001b[0m\r\n"; console.log(errorMessage); var obj = {}; Error.captureStackTrace(obj);