437 lines
12 KiB
ReasonML
437 lines
12 KiB
ReasonML
open Rationale.Function.Infix;
|
|
|
|
module FloatFloatMap = {
|
|
module Id =
|
|
Belt.Id.MakeComparable({
|
|
type t = float;
|
|
let cmp: (float, float) => int = Pervasives.compare;
|
|
});
|
|
|
|
type t = Belt.MutableMap.t(Id.t, float, Id.identity);
|
|
|
|
let fromArray = (ar: array((float, float))) =>
|
|
Belt.MutableMap.fromArray(ar, ~id=(module Id));
|
|
let toArray = (t: t) => Belt.MutableMap.toArray(t);
|
|
let empty = () => Belt.MutableMap.make(~id=(module Id));
|
|
let increment = (el, t: t) =>
|
|
Belt.MutableMap.update(
|
|
t,
|
|
el,
|
|
fun
|
|
| Some(n) => Some(n +. 1.0)
|
|
| None => Some(1.0),
|
|
);
|
|
|
|
let get = (el, t: t) => Belt.MutableMap.get(t, el);
|
|
let fmap = (fn, t: t) => Belt.MutableMap.map(t, fn);
|
|
};
|
|
|
|
/* Utils */
|
|
module U = {
|
|
let isEqual = (a, b) => a == b;
|
|
let toA = a => [|a|];
|
|
let id = e => e;
|
|
};
|
|
|
|
module O = {
|
|
let dimap = (sFn, rFn, e) =>
|
|
switch (e) {
|
|
| Some(r) => sFn(r)
|
|
| None => rFn()
|
|
};
|
|
();
|
|
let fmap = Rationale.Option.fmap;
|
|
let bind = Rationale.Option.bind;
|
|
let default = Rationale.Option.default;
|
|
let isSome = Rationale.Option.isSome;
|
|
let isNone = Rationale.Option.isNone;
|
|
let toExn = Rationale.Option.toExn;
|
|
let some = Rationale.Option.some;
|
|
let firstSome = Rationale.Option.firstSome;
|
|
let toExt = Rationale.Option.toExn;
|
|
let flatApply = (fn, b) =>
|
|
Rationale.Option.apply(fn, Some(b)) |> Rationale.Option.flatten;
|
|
|
|
let toBool = opt =>
|
|
switch (opt) {
|
|
| Some(_) => true
|
|
| _ => false
|
|
};
|
|
|
|
let ffmap = (fn, r) =>
|
|
switch (r) {
|
|
| Some(sm) => fn(sm)
|
|
| _ => None
|
|
};
|
|
|
|
let toString = opt =>
|
|
switch (opt) {
|
|
| Some(s) => s
|
|
| _ => ""
|
|
};
|
|
|
|
let toResult = (error, e) =>
|
|
switch (e) {
|
|
| Some(r) => Belt.Result.Ok(r)
|
|
| None => Error(error)
|
|
};
|
|
|
|
module React = {
|
|
let defaultNull = default(ReasonReact.null);
|
|
let fmapOrNull = fn => fmap(fn) ||> default(ReasonReact.null);
|
|
let flatten = default(ReasonReact.null);
|
|
};
|
|
};
|
|
|
|
/* Functions */
|
|
module F = {
|
|
let apply = (a, e) => a |> e;
|
|
|
|
let flatten2Callbacks = (fn1, fn2, fnlast) =>
|
|
fn1(response1 => fn2(response2 => fnlast(response1, response2)));
|
|
|
|
let flatten3Callbacks = (fn1, fn2, fn3, fnlast) =>
|
|
fn1(response1 =>
|
|
fn2(response2 =>
|
|
fn3(response3 => fnlast(response1, response2, response3))
|
|
)
|
|
);
|
|
|
|
let flatten4Callbacks = (fn1, fn2, fn3, fn4, fnlast) =>
|
|
fn1(response1 =>
|
|
fn2(response2 =>
|
|
fn3(response3 =>
|
|
fn4(response4 => fnlast(response1, response2, response3, response4))
|
|
)
|
|
)
|
|
);
|
|
};
|
|
|
|
module Bool = {
|
|
type t = bool;
|
|
let toString = (t: t) => t ? "TRUE" : "FALSE";
|
|
let fromString = str => str == "TRUE" ? true : false;
|
|
|
|
module O = {
|
|
let toBool = opt =>
|
|
switch (opt) {
|
|
| Some(true) => true
|
|
| _ => false
|
|
};
|
|
};
|
|
};
|
|
|
|
module Float = {
|
|
let with2DigitsPrecision = Js.Float.toPrecisionWithPrecision(_, ~digits=2);
|
|
let with3DigitsPrecision = Js.Float.toPrecisionWithPrecision(_, ~digits=3);
|
|
let toFixed = Js.Float.toFixed;
|
|
let toString = Js.Float.toString;
|
|
};
|
|
|
|
module I = {
|
|
let increment = n => n + 1;
|
|
let decrement = n => n - 1;
|
|
let toString = Js.Int.toString;
|
|
};
|
|
|
|
/* R for Result */
|
|
module R = {
|
|
let result = Rationale.Result.result;
|
|
let id = e => e |> result(U.id, U.id);
|
|
let fmap = Rationale.Result.fmap;
|
|
let bind = Rationale.Result.bind;
|
|
let toOption = (e: Belt.Result.t('a, 'b)) =>
|
|
switch (e) {
|
|
| Ok(r) => Some(r)
|
|
| Error(_) => None
|
|
};
|
|
};
|
|
|
|
let safe_fn_of_string = (fn, s: string): option('a) =>
|
|
try(Some(fn(s))) {
|
|
| _ => None
|
|
};
|
|
|
|
module S = {
|
|
let safe_float = float_of_string->safe_fn_of_string;
|
|
let safe_int = int_of_string->safe_fn_of_string;
|
|
let default = (defaultStr, str) => str == "" ? defaultStr : str;
|
|
};
|
|
|
|
module J = {
|
|
let toString = Js.Json.decodeString ||> O.default("");
|
|
let fromString = Js.Json.string;
|
|
let fromNumber = Js.Json.number;
|
|
|
|
module O = {
|
|
let fromString = (str: string) =>
|
|
switch (str) {
|
|
| "" => None
|
|
| _ => Some(Js.Json.string(str))
|
|
};
|
|
|
|
let toString = (str: option('a)) =>
|
|
switch (str) {
|
|
| Some(str) => Some(str |> (Js.Json.decodeString ||> O.default("")))
|
|
| _ => None
|
|
};
|
|
};
|
|
};
|
|
|
|
module M = {
|
|
let format = MomentRe.Moment.format;
|
|
let format_standard = "MMM DD, YYYY HH:mm";
|
|
let format_simple = "L";
|
|
/* TODO: Figure out better name */
|
|
let goFormat_simple = MomentRe.Moment.format(format_simple);
|
|
let goFormat_standard = MomentRe.Moment.format(format_standard);
|
|
let toUtc = MomentRe.momentUtc;
|
|
let toJSON = MomentRe.Moment.toJSON;
|
|
let momentDefaultFormat = MomentRe.momentDefaultFormat;
|
|
};
|
|
|
|
module JsDate = {
|
|
let fromString = Js.Date.fromString;
|
|
let now = Js.Date.now;
|
|
let make = Js.Date.make;
|
|
let valueOf = Js.Date.valueOf;
|
|
};
|
|
|
|
/* List */
|
|
module L = {
|
|
let fmap = List.map;
|
|
let get = Belt.List.get;
|
|
let toArray = Array.of_list;
|
|
let fmapi = List.mapi;
|
|
let concat = List.concat;
|
|
let append = List.append;
|
|
let drop = Rationale.RList.drop;
|
|
let remove = Rationale.RList.remove;
|
|
let find = List.find;
|
|
let filter = List.filter;
|
|
let for_all = List.for_all;
|
|
let exists = List.exists;
|
|
let sort = List.sort;
|
|
let length = List.length;
|
|
let filter_opt = Rationale.RList.filter_opt;
|
|
let uniqBy = Rationale.RList.uniqBy;
|
|
let join = Rationale.RList.join;
|
|
let head = Rationale.RList.head;
|
|
let uniq = Rationale.RList.uniq;
|
|
let flatten = List.flatten;
|
|
let last = Rationale.RList.last;
|
|
let append = List.append;
|
|
let getBy = Belt.List.getBy;
|
|
let dropLast = Rationale.RList.dropLast;
|
|
let contains = Rationale.RList.contains;
|
|
let without = Rationale.RList.without;
|
|
let update = Rationale.RList.update;
|
|
let iter = List.iter;
|
|
let findIndex = Rationale.RList.findIndex;
|
|
};
|
|
|
|
/* A for Array */
|
|
module A = {
|
|
let fmap = Array.map;
|
|
let fmapi = Array.mapi;
|
|
let to_list = Array.to_list;
|
|
let of_list = Array.of_list;
|
|
let length = Array.length;
|
|
let append = Array.append;
|
|
let empty = [||];
|
|
let unsafe_get = Array.unsafe_get;
|
|
let get = Belt.Array.get;
|
|
let getBy = Belt.Array.getBy;
|
|
let last = a => get(a, length(a) - 1);
|
|
let first = get(_, 0);
|
|
let hasBy = (r, fn) => Belt.Array.getBy(r, fn) |> O.isSome;
|
|
let fold_left = Array.fold_left;
|
|
let fold_right = Array.fold_right;
|
|
let concatMany = Belt.Array.concatMany;
|
|
let keepMap = Belt.Array.keepMap;
|
|
let min = a =>
|
|
get(a, 0)
|
|
|> O.fmap(first => Belt.Array.reduce(a, first, (i, j) => i < j ? i : j));
|
|
let max = a =>
|
|
get(a, 0)
|
|
|> O.fmap(first => Belt.Array.reduce(a, first, (i, j) => i > j ? i : j));
|
|
let stableSortBy = Belt.SortArray.stableSortBy;
|
|
let toRanges = (a: array('a)) =>
|
|
switch (a |> Belt.Array.length) {
|
|
| 0
|
|
| 1 => Belt.Result.Error("Must be at least 2 elements")
|
|
| n =>
|
|
Belt.Array.makeBy(n - 1, r => r)
|
|
|> Belt.Array.map(_, index =>
|
|
(
|
|
Belt.Array.getUnsafe(a, index),
|
|
Belt.Array.getUnsafe(a, index + 1),
|
|
)
|
|
)
|
|
|> Rationale.Result.return
|
|
};
|
|
|
|
let asList = (f: list('a) => list('a), r: array('a)) =>
|
|
r |> to_list |> f |> of_list;
|
|
/* TODO: Is there a better way of doing this? */
|
|
let uniq = r => asList(L.uniq, r);
|
|
|
|
//intersperse([1,2,3], [10,11,12]) => [1,10,2,11,3,12]
|
|
let intersperse = (a: array('a), b: array('a)) => {
|
|
let items: ref(array('a)) = ref([||]);
|
|
|
|
Belt.Array.forEachWithIndex(a, (i, item) => {
|
|
switch (Belt.Array.get(b, i)) {
|
|
| Some(r) => items := append(items^, [|item, r|])
|
|
| None => items := append(items^, [|item|])
|
|
}
|
|
});
|
|
items^;
|
|
};
|
|
|
|
// @todo: Is -1 still the indicator that this is false (as is true with
|
|
// @todo: js findIndex)? Wasn't sure.
|
|
let findIndex = (e, i) =>
|
|
Js.Array.findIndex(e, i)
|
|
|> (
|
|
r =>
|
|
switch (r) {
|
|
| (-1) => None
|
|
| r => Some(r)
|
|
}
|
|
);
|
|
let filter = (o, e) => Js.Array.filter(o, e);
|
|
|
|
module O = {
|
|
let concatSomes = (optionals: array(option('a))): array('a) =>
|
|
optionals
|
|
|> Js.Array.filter(Rationale.Option.isSome)
|
|
|> Js.Array.map(
|
|
Rationale.Option.toExn("Warning: This should not have happened"),
|
|
);
|
|
let defaultEmpty = (o: option(array('a))): array('a) =>
|
|
switch (o) {
|
|
| Some(o) => o
|
|
| None => [||]
|
|
};
|
|
};
|
|
|
|
module R = {
|
|
let firstErrorOrOpen =
|
|
(results: array(Belt.Result.t('a, 'b)))
|
|
: Belt.Result.t(array('a), 'b) => {
|
|
let bringErrorUp =
|
|
switch (results |> Belt.Array.getBy(_, Belt.Result.isError)) {
|
|
| Some(Belt.Result.Error(err)) => Belt.Result.Error(err)
|
|
| Some(Belt.Result.Ok(_)) => Belt.Result.Ok(results)
|
|
| None => Belt.Result.Ok(results)
|
|
};
|
|
let forceOpen = (r: array(Belt.Result.t('a, 'b))): array('a) =>
|
|
r |> Belt.Array.map(_, r => Belt.Result.getExn(r));
|
|
bringErrorUp |> Belt.Result.map(_, forceOpen);
|
|
};
|
|
};
|
|
|
|
module Sorted = {
|
|
let min = first;
|
|
let max = last;
|
|
let range = (~min=min, ~max=max, a) =>
|
|
switch (min(a), max(a)) {
|
|
| (Some(min), Some(max)) => Some(max -. min)
|
|
| _ => None
|
|
};
|
|
let binarySearchFirstElementGreaterIndex = (ar: array('a), el: 'a) => {
|
|
let el = Belt.SortArray.binarySearchBy(ar, el, compare);
|
|
let el = el < 0 ? el * (-1) - 1 : el;
|
|
switch (el) {
|
|
| e when e >= length(ar) => `overMax
|
|
| e when e == 0 => `underMin
|
|
| e => `firstHigher(e)
|
|
};
|
|
};
|
|
|
|
let concat = (t1: array('a), t2: array('a)) => {
|
|
let ts = Belt.Array.concat(t1, t2);
|
|
ts |> Array.fast_sort(compare);
|
|
ts;
|
|
};
|
|
|
|
module Floats = {
|
|
let makeIncrementalUp = (a, b) =>
|
|
Array.make(b - a + 1, a)
|
|
|> Array.mapi((i, c) => c + i)
|
|
|> Belt.Array.map(_, float_of_int);
|
|
|
|
let makeIncrementalDown = (a, b) =>
|
|
Array.make(a - b + 1, a)
|
|
|> Array.mapi((i, c) => c - i)
|
|
|> Belt.Array.map(_, float_of_int);
|
|
|
|
let split = (sortedArray: array(float)) => {
|
|
let continuous = [||];
|
|
let discrete = FloatFloatMap.empty();
|
|
Belt.Array.forEachWithIndex(
|
|
sortedArray,
|
|
(index, element) => {
|
|
let maxIndex = (sortedArray |> Array.length) - 1;
|
|
let possiblySimilarElements =
|
|
(
|
|
switch (index) {
|
|
| 0 => [|index + 1|]
|
|
| n when n == maxIndex => [|index - 1|]
|
|
| _ => [|index - 1, index + 1|]
|
|
}
|
|
)
|
|
|> Belt.Array.map(_, r => sortedArray[r]);
|
|
let hasSimilarElement =
|
|
Belt.Array.some(possiblySimilarElements, r => r == element);
|
|
hasSimilarElement
|
|
? FloatFloatMap.increment(element, discrete)
|
|
: {
|
|
let _ = Js.Array.push(element, continuous);
|
|
();
|
|
};
|
|
();
|
|
},
|
|
);
|
|
|
|
(continuous, discrete);
|
|
};
|
|
};
|
|
};
|
|
|
|
module Floats = {
|
|
let sum = Belt.Array.reduce(_, 0., (i, j) => i +. j);
|
|
let mean = a => sum(a) /. (Array.length(a) |> float_of_int);
|
|
let random = Js.Math.random_int;
|
|
|
|
exception RangeError(string);
|
|
let range = (min: float, max: float, n: int): array(float) => {
|
|
switch (n) {
|
|
| 0 => [||]
|
|
| 1 => [|min|]
|
|
| 2 => [|min, max|]
|
|
| _ when min == max => Belt.Array.make(n, min)
|
|
| _ when n < 0 => raise(RangeError("n must be greater than 0"))
|
|
| _ when min > max =>
|
|
raise(RangeError("Min value is less then max value"))
|
|
| _ =>
|
|
let diff = (max -. min) /. Belt.Float.fromInt(n - 1);
|
|
Belt.Array.makeBy(n, i => {min +. Belt.Float.fromInt(i) *. diff});
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
module JsArray = {
|
|
let concatSomes = (optionals: Js.Array.t(option('a))): Js.Array.t('a) =>
|
|
optionals
|
|
|> Js.Array.filter(Rationale.Option.isSome)
|
|
|> Js.Array.map(
|
|
Rationale.Option.toExn("Warning: This should not have happened"),
|
|
);
|
|
let filter = Js.Array.filter;
|
|
};
|
|
|
|
let ste = React.string;
|
|
let showIf = (cond, comp) => cond ? comp : ReasonReact.null; |