From dde28e54f084c751bdbc24a75cf625efabbe1e7b Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Thu, 28 Apr 2022 10:17:29 -0400 Subject: [PATCH 1/6] Restructuring of E Sorted --- .../AlgebraicShapeCombination.res | 2 +- .../SampleSetDist_ToPointSet.res | 2 +- .../squiggle-lang/src/rescript/Utility/E.res | 126 +++++++++--------- 3 files changed, 63 insertions(+), 67 deletions(-) diff --git a/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/AlgebraicShapeCombination.res b/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/AlgebraicShapeCombination.res index 63600e43..a51de00d 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/AlgebraicShapeCombination.res +++ b/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/AlgebraicShapeCombination.res @@ -263,4 +263,4 @@ let combineShapesContinuousDiscrete = ( ) } -let isOrdered = (a: XYShape.T.t): bool => E.A.Sorted.Floats.isSorted(a.xs) +let isOrdered = (a: XYShape.T.t): bool => E.A.Floats.isSorted(a.xs) diff --git a/packages/squiggle-lang/src/rescript/Distributions/SampleSetDist/SampleSetDist_ToPointSet.res b/packages/squiggle-lang/src/rescript/Distributions/SampleSetDist/SampleSetDist_ToPointSet.res index 90537a12..da9f941f 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/SampleSetDist/SampleSetDist_ToPointSet.res +++ b/packages/squiggle-lang/src/rescript/Distributions/SampleSetDist/SampleSetDist_ToPointSet.res @@ -85,7 +85,7 @@ let toPointSetDist = ( (), ): Internals.Types.outputs => { Array.fast_sort(compare, samples) - let (continuousPart, discretePart) = E.A.Sorted.Floats.split(samples) + let (continuousPart, discretePart) = E.A.Sorted.split(samples) let length = samples |> E.A.length |> float_of_int let discrete: PointSetTypes.discreteShape = discretePart diff --git a/packages/squiggle-lang/src/rescript/Utility/E.res b/packages/squiggle-lang/src/rescript/Utility/E.res index 030c2961..9c11426a 100644 --- a/packages/squiggle-lang/src/rescript/Utility/E.res +++ b/packages/squiggle-lang/src/rescript/Utility/E.res @@ -475,42 +475,73 @@ module A = { } } - 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 - } + module Floats = { + let mean = Jstat.mean + let geomean = Jstat.geomean + let mode = Jstat.mode + let variance = Jstat.variance + let stdev = Jstat.stdev + let sum = Jstat.sum + let random = Js.Math.random_int let floatCompare: (float, float) => int = compare + let sort = t => { + let r = t + r |> Array.fast_sort(floatCompare) + r + } - let binarySearchFirstElementGreaterIndex = (ar: array<'a>, el: 'a) => { - let el = Belt.SortArray.binarySearchBy(ar, el, floatCompare) - let el = el < 0 ? el * -1 - 1 : el - switch el { - | e if e >= length(ar) => #overMax - | e if e == 0 => #underMin - | e => #firstHigher(e) + let isSorted = (ar: array): bool => + reduce(zip(ar, tail(ar)), true, (acc, (first, second)) => acc && first < second) + + //Passing true for the exclusive parameter excludes both endpoints of the range. + //https://jstat.github.io/all.html + let percentile = (a, b) => Jstat.percentile(a, b, false) + + // Gives an array with all the differences between values + // diff([1,5,3,7]) = [4,-2,4] + let diff = (arr: array): array => + Belt.Array.zipBy(arr, Belt.Array.sliceToEnd(arr, 1), (left, right) => right -. left) + + exception RangeError(string) + let range = (min: float, max: float, n: int): array => + switch n { + | 0 => [] + | 1 => [min] + | 2 => [min, max] + | _ if min == max => Belt.Array.make(n, min) + | _ if n < 0 => raise(RangeError("n must be greater than 0")) + | _ if 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) } - } - let concat = (t1: array<'a>, t2: array<'a>) => { - let ts = Belt.Array.concat(t1, t2) - ts |> Array.fast_sort(floatCompare) - ts - } + let min = Js.Math.minMany_float + let max = Js.Math.maxMany_float - let concatMany = (t1: array>) => { - let ts = Belt.Array.concatMany(t1) - ts |> Array.fast_sort(floatCompare) - ts - } + 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 + } - module Floats = { - let isSorted = (ar: array): bool => - reduce(zip(ar, tail(ar)), true, (acc, (first, second)) => acc && first < second) + let binarySearchFirstElementGreaterIndex = (ar: array<'a>, el: 'a) => { + let el = Belt.SortArray.binarySearchBy(ar, el, floatCompare) + let el = el < 0 ? el * -1 - 1 : el + switch el { + | e if e >= length(ar) => #overMax + | e if e == 0 => #underMin + | e => #firstHigher(e) + } + } + + let concat = (t1: array<'a>, t2: array<'a>) => Belt.Array.concat(t1, t2)->sort + + let concatMany = (t1: array>) => Belt.Array.concatMany(t1)->sort let makeIncrementalUp = (a, b) => Array.make(b - a + 1, a) |> Array.mapi((i, c) => c + i) |> Belt.Array.map(_, float_of_int) @@ -543,42 +574,7 @@ module A = { } } } - - module Floats = { - let mean = Jstat.mean - let geomean = Jstat.geomean - let mode = Jstat.mode - let variance = Jstat.variance - let stdev = Jstat.stdev - let sum = Jstat.sum - let random = Js.Math.random_int - - //Passing true for the exclusive parameter excludes both endpoints of the range. - //https://jstat.github.io/all.html - let percentile = (a, b) => Jstat.percentile(a, b, false) - - // Gives an array with all the differences between values - // diff([1,5,3,7]) = [4,-2,4] - let diff = (arr: array): array => - Belt.Array.zipBy(arr, Belt.Array.sliceToEnd(arr, 1), (left, right) => right -. left) - - exception RangeError(string) - let range = (min: float, max: float, n: int): array => - switch n { - | 0 => [] - | 1 => [min] - | 2 => [min, max] - | _ if min == max => Belt.Array.make(n, min) - | _ if n < 0 => raise(RangeError("n must be greater than 0")) - | _ if 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) - } - - let min = Js.Math.minMany_float - let max = Js.Math.maxMany_float - } + module Sorted = Floats.Sorted; } module A2 = { From d1ffac492cb0c4147ace407c3f36418081459887 Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Thu, 28 Apr 2022 11:39:29 -0400 Subject: [PATCH 2/6] Draft of Validates for XYShape --- packages/squiggle-lang/src/rescript/Utility/E.res | 14 +++++++++----- .../squiggle-lang/src/rescript/Utility/XYShape.res | 10 ++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/squiggle-lang/src/rescript/Utility/E.res b/packages/squiggle-lang/src/rescript/Utility/E.res index 9c11426a..45051578 100644 --- a/packages/squiggle-lang/src/rescript/Utility/E.res +++ b/packages/squiggle-lang/src/rescript/Utility/E.res @@ -476,6 +476,7 @@ module A = { } module Floats = { + type t = array let mean = Jstat.mean let geomean = Jstat.geomean let mode = Jstat.mode @@ -491,8 +492,11 @@ module A = { r } - let isSorted = (ar: array): bool => - reduce(zip(ar, tail(ar)), true, (acc, (first, second)) => acc && first < second) + let getNonFinite = (t: t) => Belt.Array.getBy(t, r => !Js.Float.isFinite(r)) + let getBelowZero = (t: t) => Belt.Array.getBy(t, r => r < 0.0) + + let isSorted = (t: t): bool => + reduce(zip(t, tail(t)), true, (acc, (first, second)) => acc && first < second) //Passing true for the exclusive parameter excludes both endpoints of the range. //https://jstat.github.io/all.html @@ -500,8 +504,8 @@ module A = { // Gives an array with all the differences between values // diff([1,5,3,7]) = [4,-2,4] - let diff = (arr: array): array => - Belt.Array.zipBy(arr, Belt.Array.sliceToEnd(arr, 1), (left, right) => right -. left) + let diff = (t: t): array => + Belt.Array.zipBy(t, Belt.Array.sliceToEnd(t, 1), (left, right) => right -. left) exception RangeError(string) let range = (min: float, max: float, n: int): array => @@ -574,7 +578,7 @@ module A = { } } } - module Sorted = Floats.Sorted; + module Sorted = Floats.Sorted } module A2 = { diff --git a/packages/squiggle-lang/src/rescript/Utility/XYShape.res b/packages/squiggle-lang/src/rescript/Utility/XYShape.res index 97974884..e22dc5fd 100644 --- a/packages/squiggle-lang/src/rescript/Utility/XYShape.res +++ b/packages/squiggle-lang/src/rescript/Utility/XYShape.res @@ -62,6 +62,16 @@ module T = { let toJs = (t: t) => {"xs": t.xs, "ys": t.ys} } +module Validates = { + type t = T.t + let areXsSorted = (t:t) => E.A.Floats.isSorted(T.xs(t)) + let validate = (t:t) => { + let xsNotSorted = E.A.Floats.isSorted(T.xs(t)) ? None : Some("Xs are not sorted") + let xsNotFinite = E.A.Floats.getNonFinite(T.xs(t)) |> E.O.fmap(r => `Xs contain non-finite values: ${E.Float.toString(r)}`) + let ysNotFinite = E.A.Floats.getNonFinite(T.ys(t)) |> E.O.fmap(r => `Ys contain non-finite values: ${E.Float.toString(r)}`) + } +} + module Ts = { type t = T.ts let minX = (t: t) => t |> E.A.fmap(T.minX) |> E.A.Floats.min From 03cd887084db7354751312bd9c3e43872c36354b Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Thu, 28 Apr 2022 12:47:04 -0400 Subject: [PATCH 3/6] Adding better error messages to XYShape validator --- .../squiggle-lang/src/rescript/Utility/E.res | 1 + .../src/rescript/Utility/Errors.res | 29 ++++++++++++++ .../src/rescript/Utility/XYShape.res | 38 ++++++++++++++++--- 3 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 packages/squiggle-lang/src/rescript/Utility/Errors.res diff --git a/packages/squiggle-lang/src/rescript/Utility/E.res b/packages/squiggle-lang/src/rescript/Utility/E.res index 45051578..88024c3b 100644 --- a/packages/squiggle-lang/src/rescript/Utility/E.res +++ b/packages/squiggle-lang/src/rescript/Utility/E.res @@ -584,6 +584,7 @@ module A = { module A2 = { let fmap = (a, b) => A.fmap(b, a) let joinWith = (a, b) => A.joinWith(b, a) + let filter = (a,b) => A.filter(b, a) } module JsArray = { diff --git a/packages/squiggle-lang/src/rescript/Utility/Errors.res b/packages/squiggle-lang/src/rescript/Utility/Errors.res new file mode 100644 index 00000000..6f52b21c --- /dev/null +++ b/packages/squiggle-lang/src/rescript/Utility/Errors.res @@ -0,0 +1,29 @@ +type property = {fnName: string, propertyName: string} + +type rec error = + | NotSorted(property) + | IsEmpty(property) + | NotFinite(property, float) + | DifferentLengths({fnName: string, p1Name: string, p2Name: string, p1Length: int, p2Length: int}) + | Multiple(array) + +let mapErrorArrayToError = (errors: array): option => { + switch errors { + | [] => None + | [error] => Some(error) + | _ => Some(Multiple(errors)) + } +} + +let rec toString = (t: error) => + switch t { + | NotSorted({fnName, propertyName}) => `${fnName} ${propertyName} is not sorted` + | IsEmpty({fnName, propertyName}) => `${fnName} ${propertyName} is empty` + | NotFinite({fnName, propertyName}, exampleValue) => + `${fnName} ${propertyName} is not finite. Example value: ${E.Float.toString(exampleValue)}` + | DifferentLengths({fnName, p1Name, p2Name, p1Length, p2Length}) => + `${fnName} ${p1Name} and ${p2Name} have different lengths. ${p1Name} has length ${E.I.toString( + p1Length, + )} and ${p2Name} has length ${E.I.toString(p2Length)}` + | Multiple(errors) => `Multiple Errors: ${E.A2.fmap(errors, toString) |> E.A.joinWith(", ")}` + } diff --git a/packages/squiggle-lang/src/rescript/Utility/XYShape.res b/packages/squiggle-lang/src/rescript/Utility/XYShape.res index e22dc5fd..9368c1ee 100644 --- a/packages/squiggle-lang/src/rescript/Utility/XYShape.res +++ b/packages/squiggle-lang/src/rescript/Utility/XYShape.res @@ -62,13 +62,39 @@ module T = { let toJs = (t: t) => {"xs": t.xs, "ys": t.ys} } -module Validates = { +module Validator = { type t = T.t - let areXsSorted = (t:t) => E.A.Floats.isSorted(T.xs(t)) - let validate = (t:t) => { - let xsNotSorted = E.A.Floats.isSorted(T.xs(t)) ? None : Some("Xs are not sorted") - let xsNotFinite = E.A.Floats.getNonFinite(T.xs(t)) |> E.O.fmap(r => `Xs contain non-finite values: ${E.Float.toString(r)}`) - let ysNotFinite = E.A.Floats.getNonFinite(T.ys(t)) |> E.O.fmap(r => `Ys contain non-finite values: ${E.Float.toString(r)}`) + let fnName = "XYShape validate" + let property = (propertyName: string): Errors.property => { + fnName: fnName, + propertyName: propertyName, + } + let notSortedError = (p: string): Errors.error => NotSorted(property(p)) + let notFiniteError = (p, exampleValue): Errors.error => NotFinite(property(p), exampleValue) + let isEmptyError = (propertyName): Errors.error => IsEmpty(property(propertyName)) + let differentLengthsError = (t): Errors.error => DifferentLengths({ + fnName: fnName, + p1Name: "Xs", + p2Name: "Ys", + p1Length: E.A.length(T.xs(t)), + p2Length: E.A.length(T.ys(t)), + }) + + let areXsSorted = (t: t) => E.A.Floats.isSorted(T.xs(t)) + let areXsEmpty = (t: t) => E.A.length(t.xs) == 0 + let getNonFiniteXs = (t: t) => t->T.xs->E.A.Floats.getNonFinite + let getNonFiniteYs = (t: t) => t->T.ys->E.A.Floats.getNonFinite + + let validate = (t: t) => { + let xsNotSorted = areXsSorted(t) ? None : Some(notSortedError("Xs")) + let xsEmpty = areXsEmpty(t) ? None : Some(isEmptyError("Xs")) + let differentLengths = + E.A.length(T.xs(t)) !== E.A.length(T.ys(t)) ? Some(differentLengthsError(t)) : None + let xsNotFinite = getNonFiniteXs(t)->E.O2.fmap(notFiniteError("Xs")) + let ysNotFinite = getNonFiniteYs(t)->E.O2.fmap(notFiniteError("Ys")) + [xsNotSorted, xsEmpty, differentLengths, xsNotFinite, ysNotFinite] + ->E.A.O.concatSomes + ->Errors.mapErrorArrayToError } } From 5dd0292b52fb9cbabe22bd5e886b6c7dd40c321e Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Thu, 28 Apr 2022 14:52:44 -0400 Subject: [PATCH 4/6] Added tests for XYShape validator --- .../squiggle-lang/__tests__/XYShape_test.res | 28 ++++++--- .../squiggle-lang/src/rescript/Utility/E.res | 8 ++- .../src/rescript/Utility/Errors.res | 6 +- .../src/rescript/Utility/XYShape.res | 63 +++++++++---------- 4 files changed, 59 insertions(+), 46 deletions(-) diff --git a/packages/squiggle-lang/__tests__/XYShape_test.res b/packages/squiggle-lang/__tests__/XYShape_test.res index 701d82e1..9f5be4fd 100644 --- a/packages/squiggle-lang/__tests__/XYShape_test.res +++ b/packages/squiggle-lang/__tests__/XYShape_test.res @@ -19,6 +19,24 @@ let pointSetDist3: PointSetTypes.xyShape = { } describe("XYShapes", () => { + describe("Validator", () => { + makeTest("with no errors", XYShape.T.Validator.validate(pointSetDist1), None) + makeTest( + "when empty", + XYShape.T.Validator.validate({xs: [], ys: []})->E.O2.fmap(Errors.toString), + Some("XYShape validate Xs is empty"), + ) + makeTest( + "when not sorted, different lengths, and not finite", + XYShape.T.Validator.validate({xs: [2.0, 1.0, infinity, 0.0], ys: [3.0, infinity]})->E.O2.fmap( + Errors.toString, + ), + Some( + "Multiple Errors: [XYShape validate Xs is not sorted], [XYShape validate Xs and Ys have different lengths. Xs has length 4 and Ys has length 2], [XYShape validate Xs is not finite. Example value: Infinity], [XYShape validate Ys is not finite. Example value: Infinity]", + ), + ) + }) + describe("logScorePoint", () => { makeTest("When identical", XYShape.logScorePoint(30, pointSetDist1, pointSetDist1), Some(0.0)) makeTest( @@ -32,16 +50,6 @@ describe("XYShapes", () => { Some(210.3721280423322), ) }) - // describe("transverse", () => { - // makeTest( - // "When very different", - // XYShape.Transversal._transverse( - // (aCurrent, aLast) => aCurrent +. aLast, - // [|1.0, 2.0, 3.0, 4.0|], - // ), - // [|1.0, 3.0, 6.0, 10.0|], - // ) - // }); describe("integrateWithTriangles", () => makeTest( "integrates correctly", diff --git a/packages/squiggle-lang/src/rescript/Utility/E.res b/packages/squiggle-lang/src/rescript/Utility/E.res index af3d9f7f..aab3d160 100644 --- a/packages/squiggle-lang/src/rescript/Utility/E.res +++ b/packages/squiggle-lang/src/rescript/Utility/E.res @@ -500,7 +500,11 @@ module A = { let getBelowZero = (t: t) => Belt.Array.getBy(t, r => r < 0.0) let isSorted = (t: t): bool => - reduce(zip(t, tail(t)), true, (acc, (first, second)) => acc && first < second) + if Array.length(t) < 1 { + true + } else { + reduce(zip(t, tail(t)), true, (acc, (first, second)) => acc && first < second) + } //Passing true for the exclusive parameter excludes both endpoints of the range. //https://jstat.github.io/all.html @@ -618,7 +622,7 @@ module A = { module A2 = { let fmap = (a, b) => A.fmap(b, a) let joinWith = (a, b) => A.joinWith(b, a) - let filter = (a,b) => A.filter(b, a) + let filter = (a, b) => A.filter(b, a) } module JsArray = { diff --git a/packages/squiggle-lang/src/rescript/Utility/Errors.res b/packages/squiggle-lang/src/rescript/Utility/Errors.res index 6f52b21c..bdd41869 100644 --- a/packages/squiggle-lang/src/rescript/Utility/Errors.res +++ b/packages/squiggle-lang/src/rescript/Utility/Errors.res @@ -20,10 +20,12 @@ let rec toString = (t: error) => | NotSorted({fnName, propertyName}) => `${fnName} ${propertyName} is not sorted` | IsEmpty({fnName, propertyName}) => `${fnName} ${propertyName} is empty` | NotFinite({fnName, propertyName}, exampleValue) => - `${fnName} ${propertyName} is not finite. Example value: ${E.Float.toString(exampleValue)}` + `${fnName} ${propertyName} is not finite. Example value: ${E.Float.toString(exampleValue)}` | DifferentLengths({fnName, p1Name, p2Name, p1Length, p2Length}) => `${fnName} ${p1Name} and ${p2Name} have different lengths. ${p1Name} has length ${E.I.toString( p1Length, )} and ${p2Name} has length ${E.I.toString(p2Length)}` - | Multiple(errors) => `Multiple Errors: ${E.A2.fmap(errors, toString) |> E.A.joinWith(", ")}` + | Multiple(errors) => + `Multiple Errors: ${E.A2.fmap(errors, toString)->E.A2.fmap(r => `[${r}]`) + |> E.A.joinWith(", ")}` } diff --git a/packages/squiggle-lang/src/rescript/Utility/XYShape.res b/packages/squiggle-lang/src/rescript/Utility/XYShape.res index 9368c1ee..1d96a3ee 100644 --- a/packages/squiggle-lang/src/rescript/Utility/XYShape.res +++ b/packages/squiggle-lang/src/rescript/Utility/XYShape.res @@ -60,41 +60,40 @@ module T = { let fromZippedArray = (pairs: array<(float, float)>): t => pairs |> Belt.Array.unzip |> fromArray let equallyDividedXs = (t: t, newLength) => E.A.Floats.range(minX(t), maxX(t), newLength) let toJs = (t: t) => {"xs": t.xs, "ys": t.ys} -} -module Validator = { - type t = T.t - let fnName = "XYShape validate" - let property = (propertyName: string): Errors.property => { - fnName: fnName, - propertyName: propertyName, - } - let notSortedError = (p: string): Errors.error => NotSorted(property(p)) - let notFiniteError = (p, exampleValue): Errors.error => NotFinite(property(p), exampleValue) - let isEmptyError = (propertyName): Errors.error => IsEmpty(property(propertyName)) - let differentLengthsError = (t): Errors.error => DifferentLengths({ - fnName: fnName, - p1Name: "Xs", - p2Name: "Ys", - p1Length: E.A.length(T.xs(t)), - p2Length: E.A.length(T.ys(t)), - }) + module Validator = { + let fnName = "XYShape validate" + let property = (propertyName: string): Errors.property => { + fnName: fnName, + propertyName: propertyName, + } + let notSortedError = (p: string): Errors.error => NotSorted(property(p)) + let notFiniteError = (p, exampleValue): Errors.error => NotFinite(property(p), exampleValue) + let isEmptyError = (propertyName): Errors.error => IsEmpty(property(propertyName)) + let differentLengthsError = (t): Errors.error => DifferentLengths({ + fnName: fnName, + p1Name: "Xs", + p2Name: "Ys", + p1Length: E.A.length(xs(t)), + p2Length: E.A.length(ys(t)), + }) - let areXsSorted = (t: t) => E.A.Floats.isSorted(T.xs(t)) - let areXsEmpty = (t: t) => E.A.length(t.xs) == 0 - let getNonFiniteXs = (t: t) => t->T.xs->E.A.Floats.getNonFinite - let getNonFiniteYs = (t: t) => t->T.ys->E.A.Floats.getNonFinite + let areXsSorted = (t: t) => E.A.Floats.isSorted(xs(t)) + let areXsEmpty = (t: t) => E.A.length(xs(t)) == 0 + let getNonFiniteXs = (t: t) => t->xs->E.A.Floats.getNonFinite + let getNonFiniteYs = (t: t) => t->ys->E.A.Floats.getNonFinite - let validate = (t: t) => { - let xsNotSorted = areXsSorted(t) ? None : Some(notSortedError("Xs")) - let xsEmpty = areXsEmpty(t) ? None : Some(isEmptyError("Xs")) - let differentLengths = - E.A.length(T.xs(t)) !== E.A.length(T.ys(t)) ? Some(differentLengthsError(t)) : None - let xsNotFinite = getNonFiniteXs(t)->E.O2.fmap(notFiniteError("Xs")) - let ysNotFinite = getNonFiniteYs(t)->E.O2.fmap(notFiniteError("Ys")) - [xsNotSorted, xsEmpty, differentLengths, xsNotFinite, ysNotFinite] - ->E.A.O.concatSomes - ->Errors.mapErrorArrayToError + let validate = (t: t) => { + let xsNotSorted = areXsSorted(t) ? None : Some(notSortedError("Xs")) + let xsEmpty = areXsEmpty(t) ? Some(isEmptyError("Xs")) : None + let differentLengths = + E.A.length(xs(t)) !== E.A.length(ys(t)) ? Some(differentLengthsError(t)) : None + let xsNotFinite = getNonFiniteXs(t)->E.O2.fmap(notFiniteError("Xs")) + let ysNotFinite = getNonFiniteYs(t)->E.O2.fmap(notFiniteError("Ys")) + [xsNotSorted, xsEmpty, differentLengths, xsNotFinite, ysNotFinite] + ->E.A.O.concatSomes + ->Errors.mapErrorArrayToError + } } } From 95a4bac49bf19801b9e3bed690e9b88c847ab427 Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Thu, 28 Apr 2022 15:01:57 -0400 Subject: [PATCH 5/6] Added NaN to test to make sure its caught --- packages/squiggle-lang/__tests__/XYShape_test.res | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/squiggle-lang/__tests__/XYShape_test.res b/packages/squiggle-lang/__tests__/XYShape_test.res index 9f5be4fd..c63e1ce2 100644 --- a/packages/squiggle-lang/__tests__/XYShape_test.res +++ b/packages/squiggle-lang/__tests__/XYShape_test.res @@ -28,11 +28,11 @@ describe("XYShapes", () => { ) makeTest( "when not sorted, different lengths, and not finite", - XYShape.T.Validator.validate({xs: [2.0, 1.0, infinity, 0.0], ys: [3.0, infinity]})->E.O2.fmap( + XYShape.T.Validator.validate({xs: [2.0, 1.0, infinity, 0.0], ys: [3.0, Js.Float._NaN]})->E.O2.fmap( Errors.toString, ), Some( - "Multiple Errors: [XYShape validate Xs is not sorted], [XYShape validate Xs and Ys have different lengths. Xs has length 4 and Ys has length 2], [XYShape validate Xs is not finite. Example value: Infinity], [XYShape validate Ys is not finite. Example value: Infinity]", + "Multiple Errors: [XYShape validate Xs is not sorted], [XYShape validate Xs and Ys have different lengths. Xs has length 4 and Ys has length 2], [XYShape validate Xs is not finite. Example value: Infinity], [XYShape validate Ys is not finite. Example value: NaN]", ), ) }) From 94d4a38540fa0425f1412da4f02c62ee804be671 Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Thu, 28 Apr 2022 16:49:51 -0400 Subject: [PATCH 6/6] Refactored errors for XYShape --- .../squiggle-lang/__tests__/XYShape_test.res | 17 +++--- .../Distributions/DistributionTypes.res | 2 + .../squiggle-lang/src/rescript/Utility/E.res | 6 ++ .../src/rescript/Utility/Errors.res | 31 ---------- .../src/rescript/Utility/XYShape.res | 59 +++++++++++++++---- 5 files changed, 66 insertions(+), 49 deletions(-) delete mode 100644 packages/squiggle-lang/src/rescript/Utility/Errors.res diff --git a/packages/squiggle-lang/__tests__/XYShape_test.res b/packages/squiggle-lang/__tests__/XYShape_test.res index c63e1ce2..38535020 100644 --- a/packages/squiggle-lang/__tests__/XYShape_test.res +++ b/packages/squiggle-lang/__tests__/XYShape_test.res @@ -18,21 +18,22 @@ let pointSetDist3: PointSetTypes.xyShape = { ys: [0.2, 0.5, 0.8], } +let makeAndGetErrorString = (~xs, ~ys) => + XYShape.T.make(~xs, ~ys)->E.R.getError->E.O2.fmap(XYShape.Error.toString) + describe("XYShapes", () => { describe("Validator", () => { - makeTest("with no errors", XYShape.T.Validator.validate(pointSetDist1), None) makeTest( - "when empty", - XYShape.T.Validator.validate({xs: [], ys: []})->E.O2.fmap(Errors.toString), - Some("XYShape validate Xs is empty"), + "with no errors", + makeAndGetErrorString(~xs=[1.0, 4.0, 8.0], ~ys=[0.2, 0.4, 0.8]), + None, ) + makeTest("when empty", makeAndGetErrorString(~xs=[], ~ys=[]), Some("Xs is empty")) makeTest( "when not sorted, different lengths, and not finite", - XYShape.T.Validator.validate({xs: [2.0, 1.0, infinity, 0.0], ys: [3.0, Js.Float._NaN]})->E.O2.fmap( - Errors.toString, - ), + makeAndGetErrorString(~xs=[2.0, 1.0, infinity, 0.0], ~ys=[3.0, Js.Float._NaN]), Some( - "Multiple Errors: [XYShape validate Xs is not sorted], [XYShape validate Xs and Ys have different lengths. Xs has length 4 and Ys has length 2], [XYShape validate Xs is not finite. Example value: Infinity], [XYShape validate Ys is not finite. Example value: NaN]", + "Multiple Errors: [Xs is not sorted], [Xs and Ys have different lengths. Xs has length 4 and Ys has length 2], [Xs is not finite. Example value: Infinity], [Ys is not finite. Example value: NaN]", ), ) }) diff --git a/packages/squiggle-lang/src/rescript/Distributions/DistributionTypes.res b/packages/squiggle-lang/src/rescript/Distributions/DistributionTypes.res index e27a138d..93f86798 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/DistributionTypes.res +++ b/packages/squiggle-lang/src/rescript/Distributions/DistributionTypes.res @@ -19,6 +19,7 @@ type error = | RequestedStrategyInvalidError(string) | LogarithmOfDistributionError(string) | OtherError(string) + | XYShapeError(XYShape.error) @genType module Error = { @@ -39,6 +40,7 @@ module Error = { | PointSetConversionError(err) => SampleSetDist.pointsetConversionErrorToString(err) | SparklineError(err) => PointSetTypes.sparklineErrorToString(err) | RequestedStrategyInvalidError(err) => `Requested strategy invalid: ${err}` + | XYShapeError(err) => `XY Shape Error: ${XYShape.Error.toString(err)}` | OtherError(s) => s } diff --git a/packages/squiggle-lang/src/rescript/Utility/E.res b/packages/squiggle-lang/src/rescript/Utility/E.res index aab3d160..35699c5c 100644 --- a/packages/squiggle-lang/src/rescript/Utility/E.res +++ b/packages/squiggle-lang/src/rescript/Utility/E.res @@ -162,6 +162,12 @@ exception Assertion(string) module R = { let result = Rationale.Result.result let id = e => e |> result(U.id, U.id) + let isOk = Belt.Result.isOk + let getError = (r: result<'a, 'b>) => + switch r { + | Ok(_) => None + | Error(e) => Some(e) + } let fmap = Rationale.Result.fmap let bind = Rationale.Result.bind let toExn = (msg: string, x: result<'a, 'b>): 'a => diff --git a/packages/squiggle-lang/src/rescript/Utility/Errors.res b/packages/squiggle-lang/src/rescript/Utility/Errors.res deleted file mode 100644 index bdd41869..00000000 --- a/packages/squiggle-lang/src/rescript/Utility/Errors.res +++ /dev/null @@ -1,31 +0,0 @@ -type property = {fnName: string, propertyName: string} - -type rec error = - | NotSorted(property) - | IsEmpty(property) - | NotFinite(property, float) - | DifferentLengths({fnName: string, p1Name: string, p2Name: string, p1Length: int, p2Length: int}) - | Multiple(array) - -let mapErrorArrayToError = (errors: array): option => { - switch errors { - | [] => None - | [error] => Some(error) - | _ => Some(Multiple(errors)) - } -} - -let rec toString = (t: error) => - switch t { - | NotSorted({fnName, propertyName}) => `${fnName} ${propertyName} is not sorted` - | IsEmpty({fnName, propertyName}) => `${fnName} ${propertyName} is empty` - | NotFinite({fnName, propertyName}, exampleValue) => - `${fnName} ${propertyName} is not finite. Example value: ${E.Float.toString(exampleValue)}` - | DifferentLengths({fnName, p1Name, p2Name, p1Length, p2Length}) => - `${fnName} ${p1Name} and ${p2Name} have different lengths. ${p1Name} has length ${E.I.toString( - p1Length, - )} and ${p2Name} has length ${E.I.toString(p2Length)}` - | Multiple(errors) => - `Multiple Errors: ${E.A2.fmap(errors, toString)->E.A2.fmap(r => `[${r}]`) - |> E.A.joinWith(", ")}` - } diff --git a/packages/squiggle-lang/src/rescript/Utility/XYShape.res b/packages/squiggle-lang/src/rescript/Utility/XYShape.res index 1d96a3ee..1f1e87ca 100644 --- a/packages/squiggle-lang/src/rescript/Utility/XYShape.res +++ b/packages/squiggle-lang/src/rescript/Utility/XYShape.res @@ -4,6 +4,42 @@ type xyShape = { ys: array, } +type propertyName = string + +@genType +type rec error = + | NotSorted(propertyName) + | IsEmpty(propertyName) + | NotFinite(propertyName, float) + | DifferentLengths({p1Name: string, p2Name: string, p1Length: int, p2Length: int}) + | MultipleErrors(array) + +@genType +module Error = { + let mapErrorArrayToError = (errors: array): option => { + switch errors { + | [] => None + | [error] => Some(error) + | _ => Some(MultipleErrors(errors)) + } + } + + let rec toString = (t: error) => + switch t { + | NotSorted(propertyName) => `${propertyName} is not sorted` + | IsEmpty(propertyName) => `${propertyName} is empty` + | NotFinite(propertyName, exampleValue) => + `${propertyName} is not finite. Example value: ${E.Float.toString(exampleValue)}` + | DifferentLengths({p1Name, p2Name, p1Length, p2Length}) => + `${p1Name} and ${p2Name} have different lengths. ${p1Name} has length ${E.I.toString( + p1Length, + )} and ${p2Name} has length ${E.I.toString(p2Length)}` + | MultipleErrors(errors) => + `Multiple Errors: ${E.A2.fmap(errors, toString)->E.A2.fmap(r => `[${r}]`) + |> E.A.joinWith(", ")}` + } +} + @genType type interpolationStrategy = [ | #Stepwise @@ -63,15 +99,10 @@ module T = { module Validator = { let fnName = "XYShape validate" - let property = (propertyName: string): Errors.property => { - fnName: fnName, - propertyName: propertyName, - } - let notSortedError = (p: string): Errors.error => NotSorted(property(p)) - let notFiniteError = (p, exampleValue): Errors.error => NotFinite(property(p), exampleValue) - let isEmptyError = (propertyName): Errors.error => IsEmpty(property(propertyName)) - let differentLengthsError = (t): Errors.error => DifferentLengths({ - fnName: fnName, + let notSortedError = (p: string): error => NotSorted(p) + let notFiniteError = (p, exampleValue): error => NotFinite(p, exampleValue) + let isEmptyError = (propertyName): error => IsEmpty(propertyName) + let differentLengthsError = (t): error => DifferentLengths({ p1Name: "Xs", p2Name: "Ys", p1Length: E.A.length(xs(t)), @@ -92,7 +123,15 @@ module T = { let ysNotFinite = getNonFiniteYs(t)->E.O2.fmap(notFiniteError("Ys")) [xsNotSorted, xsEmpty, differentLengths, xsNotFinite, ysNotFinite] ->E.A.O.concatSomes - ->Errors.mapErrorArrayToError + ->Error.mapErrorArrayToError + } + } + + let make = (~xs: array, ~ys: array) => { + let attempt: t = {xs: xs, ys: ys} + switch Validator.validate(attempt) { + | Some(error) => Error(error) + | None => Ok(attempt) } } }