Merge branch 'develop' into multiple-charts

This commit is contained in:
Sam Nolan 2022-08-11 12:12:59 +01:00
commit ba59047d31
40 changed files with 1687 additions and 802 deletions

View File

@ -1,5 +1,10 @@
import * as React from "react"; import * as React from "react";
import { lambdaValue, environment, runForeign } from "@quri/squiggle-lang"; import {
lambdaValue,
environment,
runForeign,
errorValueToString,
} from "@quri/squiggle-lang";
import { FunctionChart1Dist } from "./FunctionChart1Dist"; import { FunctionChart1Dist } from "./FunctionChart1Dist";
import { FunctionChart1Number } from "./FunctionChart1Number"; import { FunctionChart1Number } from "./FunctionChart1Number";
import { DistributionPlottingSettings } from "./DistributionChart"; import { DistributionPlottingSettings } from "./DistributionChart";
@ -45,10 +50,16 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
} }
}; };
const validResult = getValidResult(); const validResult = getValidResult();
const resultType =
validResult.tag === "Ok" ? validResult.value.tag : ("Error" as const);
switch (resultType) { if (validResult.tag === "Error") {
return (
<ErrorAlert heading="Error">
{errorValueToString(validResult.value)}
</ErrorAlert>
);
}
switch (validResult.value.tag) {
case "distribution": case "distribution":
return ( return (
<FunctionChart1Dist <FunctionChart1Dist
@ -68,15 +79,11 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
height={height} height={height}
/> />
); );
case "Error":
return (
<ErrorAlert heading="Error">The function failed to be run</ErrorAlert>
);
default: default:
return ( return (
<MessageAlert heading="Function Display Not Supported"> <MessageAlert heading="Function Display Not Supported">
There is no function visualization for this type of output:{" "} There is no function visualization for this type of output:{" "}
<span className="font-bold">{resultType}</span> <span className="font-bold">{validResult.value.tag}</span>
</MessageAlert> </MessageAlert>
); );
} }

View File

@ -89,7 +89,7 @@ let getPercentiles = ({ chartSettings, fn, environment }) => {
let chartPointsData: point[] = chartPointsToRender.map((x) => { let chartPointsData: point[] = chartPointsToRender.map((x) => {
let result = runForeign(fn, [x], environment); let result = runForeign(fn, [x], environment);
if (result.tag === "Ok") { if (result.tag === "Ok") {
if (result.value.tag == "distribution") { if (result.value.tag === "distribution") {
return { x, value: { tag: "Ok", value: result.value.value } }; return { x, value: { tag: "Ok", value: result.value.value } };
} else { } else {
return { return {
@ -166,12 +166,14 @@ export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({
setMouseOverlay(NaN); setMouseOverlay(NaN);
} }
const signalListeners = { mousemove: handleHover, mouseout: handleOut }; const signalListeners = { mousemove: handleHover, mouseout: handleOut };
//TODO: This custom error handling is a bit hacky and should be improved.
let mouseItem: result<squiggleExpression, errorValue> = !!mouseOverlay let mouseItem: result<squiggleExpression, errorValue> = !!mouseOverlay
? runForeign(fn, [mouseOverlay], environment) ? runForeign(fn, [mouseOverlay], environment)
: { : {
tag: "Error", tag: "Error",
value: { value: {
tag: "REExpectedType", tag: "RETodo",
value: "Hover x-coordinate returned NaN. Expected a number.", value: "Hover x-coordinate returned NaN. Expected a number.",
}, },
}; };

View File

@ -232,7 +232,7 @@ export function buildVegaSpec(
}, },
size: [{ value: 100 }], size: [{ value: 100 }],
tooltip: { tooltip: {
signal: "datum.y", signal: "{ probability: datum.y, value: datum.x }",
}, },
}, },
update: { update: {

View File

@ -21,6 +21,10 @@ describe("builtin", () => {
"addOne(t)=t+1; toList(mapSamples(fromSamples([1,2,3,4,5,6]), addOne))", "addOne(t)=t+1; toList(mapSamples(fromSamples([1,2,3,4,5,6]), addOne))",
"Ok([2,3,4,5,6,7])", "Ok([2,3,4,5,6,7])",
) )
testEval(
"toList(mapSamplesN([fromSamples([1,2,3,4,5,6]), fromSamples([6, 5, 4, 3, 2, 1])], {|x| x[0] > x[1] ? x[0] : x[1]}))",
"Ok([6,5,4,4,5,6])",
)
}) })
describe("builtin exception", () => { describe("builtin exception", () => {

View File

@ -20,7 +20,7 @@ describe("Peggy parse type", () => {
"{(::$_typeOf_$ :f (::$_typeFunction_$ (::$_constructArray_$ (#number #number #number))))}", "{(::$_typeOf_$ :f (::$_typeFunction_$ (::$_constructArray_$ (#number #number #number))))}",
) )
}) })
describe("high priority modifier", () => { describe("high priority contract", () => {
testParse( testParse(
"answer: number<-min<-max(100)|string", "answer: number<-min<-max(100)|string",
"{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ ((::$_typeModifier_max_$ (::$_typeModifier_min_$ #number) 100) #string))))}", "{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ ((::$_typeModifier_max_$ (::$_typeModifier_min_$ #number) 100) #string))))}",
@ -30,7 +30,7 @@ describe("Peggy parse type", () => {
"{(::$_typeOf_$ :answer (::$_typeModifier_memberOf_$ #number (::$_constructArray_$ (1 3 5))))}", "{(::$_typeOf_$ :answer (::$_typeModifier_memberOf_$ #number (::$_constructArray_$ (1 3 5))))}",
) )
}) })
describe("low priority modifier", () => { describe("low priority contract", () => {
testParse( testParse(
"answer: number | string $ opaque", "answer: number | string $ opaque",
"{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}", "{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}",
@ -63,14 +63,14 @@ describe("Peggy parse type", () => {
"{(::$_typeOf_$ :weekend (::$_typeOr_$ (::$_constructArray_$ ((::$_typeConstructor_$ #Saturday (::$_constructArray_$ ())) (::$_typeConstructor_$ #Sunday (::$_constructArray_$ ()))))))}", "{(::$_typeOf_$ :weekend (::$_typeOr_$ (::$_constructArray_$ ((::$_typeConstructor_$ #Saturday (::$_constructArray_$ ())) (::$_typeConstructor_$ #Sunday (::$_constructArray_$ ()))))))}",
) )
}) })
describe("type paranthesis", () => { describe("type parenthesis", () => {
//$ is introduced to avoid paranthesis //$ is introduced to avoid parenthesis
testParse( testParse(
"answer: (number|string)<-opaque", "answer: (number|string)<-opaque",
"{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}", "{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}",
) )
}) })
describe("squiggle expressions in type modifiers", () => { describe("squiggle expressions in type contracts", () => {
testParse( testParse(
"odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))", "odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))",
"{:odds1 = {(::$_constructArray_$ (1 3 5))}; :odds2 = {(::$_constructArray_$ (7 9))}; (::$_typeAlias_$ #odds (::$_typeModifier_memberOf_$ #number (::concat :odds1 :odds2)))}", "{:odds1 = {(::$_constructArray_$ (1 3 5))}; :odds2 = {(::$_constructArray_$ (7 9))}; (::$_typeAlias_$ #odds (::$_typeModifier_memberOf_$ #number (::concat :odds1 :odds2)))}",

View File

@ -3,11 +3,10 @@ module InternalExpressionValue = ReducerInterface_InternalExpressionValue
open Jest open Jest
open Reducer_Peggy_TestHelpers open Reducer_Peggy_TestHelpers
open Expect
describe("Peggy to Expression", () => { describe("Peggy to Expression", () => {
describe("literals operators parenthesis", () => { describe("literals operators parenthesis", () => {
// Note that there is always an outer block. Otherwise, external bindings are ignrored at the first statement // Note that there is always an outer block. Otherwise, external bindings are ignored at the first statement
testToExpression("1", "{1}", ~v="1", ()) testToExpression("1", "{1}", ~v="1", ())
testToExpression("'hello'", "{'hello'}", ~v="'hello'", ()) testToExpression("'hello'", "{'hello'}", ~v="'hello'", ())
testToExpression("true", "{true}", ~v="true", ()) testToExpression("true", "{true}", ~v="true", ())

View File

@ -40,7 +40,7 @@ describe("Peggy Types to Expression", () => {
(), (),
) )
}) })
describe("high priority modifier", () => { describe("high priority contract", () => {
testToExpression( testToExpression(
"answer: number<-min(1)<-max(100)|string", "answer: number<-min(1)<-max(100)|string",
"{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ ((:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 100) #string))))}", "{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ ((:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 100) #string))))}",
@ -78,7 +78,7 @@ describe("Peggy Types to Expression", () => {
(), (),
) )
}) })
describe("low priority modifier", () => { describe("low priority contract", () => {
testToExpression( testToExpression(
"answer: number | string $ opaque", "answer: number | string $ opaque",
"{(:$_typeOf_$ :answer (:$_typeModifier_opaque_$ (:$_typeOr_$ (:$_constructArray_$ (#number #string)))))}", "{(:$_typeOf_$ :answer (:$_typeModifier_opaque_$ (:$_typeOr_$ (:$_constructArray_$ (#number #string)))))}",
@ -86,7 +86,7 @@ describe("Peggy Types to Expression", () => {
(), (),
) )
}) })
describe("squiggle expressions in type modifiers", () => { describe("squiggle expressions in type contracts", () => {
testToExpression( testToExpression(
"odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))", "odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))",
"{(:$_let_$ :odds1 {(:$_constructArray_$ (1 3 5))}); (:$_let_$ :odds2 {(:$_constructArray_$ (7 9))}); (:$_typeAlias_$ #odds (:$_typeModifier_memberOf_$ #number (:concat :odds1 :odds2)))}", "{(:$_let_$ :odds1 {(:$_constructArray_$ (1 3 5))}); (:$_let_$ :odds2 {(:$_constructArray_$ (7 9))}); (:$_typeAlias_$ #odds (:$_typeModifier_memberOf_$ #number (:concat :odds1 :odds2)))}",

View File

@ -0,0 +1,52 @@
module Expression = Reducer_Expression
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Bindings = Reducer_Bindings
module T = Reducer_Type_T
module TypeCompile = Reducer_Type_Compile
open Jest
open Expect
let myIevEval = (aTypeSourceCode: string) =>
TypeCompile.ievFromTypeExpression(aTypeSourceCode, Expression.reduceExpression)
let myIevEvalToString = (aTypeSourceCode: string) =>
myIevEval(aTypeSourceCode)->InternalExpressionValue.toStringResult
let myIevExpectEqual = (aTypeSourceCode, answer) =>
expect(myIevEvalToString(aTypeSourceCode))->toEqual(answer)
let myIevTest = (test, aTypeSourceCode, answer) =>
test(aTypeSourceCode, () => myIevExpectEqual(aTypeSourceCode, answer))
let myTypeEval = (aTypeSourceCode: string) =>
TypeCompile.fromTypeExpression(aTypeSourceCode, Expression.reduceExpression)
let myTypeEvalToString = (aTypeSourceCode: string) => myTypeEval(aTypeSourceCode)->T.toStringResult
let myTypeExpectEqual = (aTypeSourceCode, answer) =>
expect(myTypeEvalToString(aTypeSourceCode))->toEqual(answer)
let myTypeTest = (test, aTypeSourceCode, answer) =>
test(aTypeSourceCode, () => myTypeExpectEqual(aTypeSourceCode, answer))
// | ItTypeIdentifier(string)
myTypeTest(test, "number", "number")
myTypeTest(test, "(number)", "number")
// | ItModifiedType({modifiedType: iType})
myIevTest(test, "number<-min(0)", "Ok({min: 0,typeIdentifier: #number,typeTag: 'typeIdentifier'})")
myTypeTest(test, "number<-min(0)", "number<-min(0)")
// | ItTypeOr({typeOr: array<iType>})
myTypeTest(test, "number | string", "(number | string)")
// | ItTypeFunction({inputs: array<iType>, output: iType})
myTypeTest(test, "number => number => number", "(number => number => number)")
// | ItTypeArray({element: iType})
myIevTest(test, "[number]", "Ok({element: #number,typeTag: 'typeArray'})")
myTypeTest(test, "[number]", "[number]")
// | ItTypeTuple({elements: array<iType>})
myTypeTest(test, "[number, string]", "[number, string]")
// | ItTypeRecord({properties: Belt.Map.String.t<iType>})
myIevTest(
test,
"{age: number, name: string}",
"Ok({properties: {age: #number,name: #string},typeTag: 'typeRecord'})",
)
myTypeTest(test, "{age: number, name: string}", "{age: number, name: string}")

View File

@ -0,0 +1,41 @@
module Expression = Reducer_Expression
module ExpressionT = Reducer_Expression_T
module ErrorValue = Reducer_ErrorValue
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Bindings = Reducer_Bindings
module T = Reducer_Type_T
module TypeChecker = Reducer_Type_TypeChecker
open Jest
open Expect
let checkArgumentsSourceCode = (aTypeSourceCode: string, sourceCode: string): result<
'v,
ErrorValue.t,
> => {
let reducerFn = Expression.reduceExpression
let rResult =
Reducer.parse(sourceCode)->Belt.Result.flatMap(expr =>
reducerFn(expr, Bindings.emptyBindings, InternalExpressionValue.defaultEnvironment)
)
rResult->Belt.Result.flatMap(result =>
switch result {
| IEvArray(args) => TypeChecker.checkArguments(aTypeSourceCode, args, reducerFn)
| _ => Js.Exn.raiseError("Arguments has to be an array")
}
)
}
let myCheckArguments = (aTypeSourceCode: string, sourceCode: string): string =>
switch checkArgumentsSourceCode(aTypeSourceCode, sourceCode) {
| Ok(_) => "Ok"
| Error(error) => ErrorValue.errorToString(error)
}
let myCheckArgumentsExpectEqual = (aTypeSourceCode, sourceCode, answer) =>
expect(myCheckArguments(aTypeSourceCode, sourceCode))->toEqual(answer)
let myCheckArgumentsTest = (test, aTypeSourceCode, sourceCode, answer) =>
test(aTypeSourceCode, () => myCheckArgumentsExpectEqual(aTypeSourceCode, sourceCode, answer))
myCheckArgumentsTest(test, "number=>number=>number", "[1,2]", "Ok")

View File

@ -0,0 +1,70 @@
module Expression = Reducer_Expression
module ExpressionT = Reducer_Expression_T
module ErrorValue = Reducer_ErrorValue
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Bindings = Reducer_Bindings
module T = Reducer_Type_T
module TypeChecker = Reducer_Type_TypeChecker
open Jest
open Expect
// In development, you are expected to use TypeChecker.isTypeOf(aTypeSourceCode, result, reducerFn).
// isTypeOfSourceCode is written to use strings instead of expression values.
let isTypeOfSourceCode = (aTypeSourceCode: string, sourceCode: string): result<
'v,
ErrorValue.t,
> => {
let reducerFn = Expression.reduceExpression
let rResult =
Reducer.parse(sourceCode)->Belt.Result.flatMap(expr =>
reducerFn(expr, Bindings.emptyBindings, InternalExpressionValue.defaultEnvironment)
)
rResult->Belt.Result.flatMap(result => TypeChecker.isTypeOf(aTypeSourceCode, result, reducerFn))
}
let myTypeCheck = (aTypeSourceCode: string, sourceCode: string): string =>
switch isTypeOfSourceCode(aTypeSourceCode, sourceCode) {
| Ok(_) => "Ok"
| Error(error) => ErrorValue.errorToString(error)
}
let myTypeCheckExpectEqual = (aTypeSourceCode, sourceCode, answer) =>
expect(myTypeCheck(aTypeSourceCode, sourceCode))->toEqual(answer)
let myTypeCheckTest = (test, aTypeSourceCode, sourceCode, answer) =>
test(aTypeSourceCode, () => myTypeCheckExpectEqual(aTypeSourceCode, sourceCode, answer))
myTypeCheckTest(test, "number", "1", "Ok")
myTypeCheckTest(test, "number", "'2'", "Expected type: number but got: '2'")
myTypeCheckTest(test, "string", "3", "Expected type: string but got: 3")
myTypeCheckTest(test, "string", "'a'", "Ok")
myTypeCheckTest(test, "[number]", "[1,2,3]", "Ok")
myTypeCheckTest(test, "[number]", "['a','a','a']", "Expected type: number but got: 'a'")
myTypeCheckTest(test, "[number]", "[1,'a',3]", "Expected type: number but got: 'a'")
myTypeCheckTest(test, "[number, string]", "[1,'a']", "Ok")
myTypeCheckTest(test, "[number, string]", "[1, 2]", "Expected type: string but got: 2")
myTypeCheckTest(
test,
"[number, string, string]",
"[1,'a']",
"Expected type: [number, string, string] but got: [1,'a']",
)
myTypeCheckTest(
test,
"[number, string]",
"[1,'a', 3]",
"Expected type: [number, string] but got: [1,'a',3]",
)
myTypeCheckTest(test, "{age: number, name: string}", "{age: 1, name: 'a'}", "Ok")
myTypeCheckTest(
test,
"{age: number, name: string}",
"{age: 1, name: 'a', job: 'IT'}",
"Expected type: {age: number, name: string} but got: {age: 1,job: 'IT',name: 'a'}",
)
myTypeCheckTest(test, "number | string", "1", "Ok")
myTypeCheckTest(test, "date | string", "1", "Expected type: (date | string) but got: 1")
myTypeCheckTest(test, "number<-min(10)", "10", "Ok")
myTypeCheckTest(test, "number<-min(10)", "0", "Expected type: number<-min(10) but got: 0")

View File

@ -10,5 +10,5 @@ describe("Evaluate ternary operator", () => {
testEvalToBe("false ? 'YES' : 'NO'", "Ok('NO')") testEvalToBe("false ? 'YES' : 'NO'", "Ok('NO')")
testEvalToBe("2 > 1 ? 'YES' : 'NO'", "Ok('YES')") testEvalToBe("2 > 1 ? 'YES' : 'NO'", "Ok('YES')")
testEvalToBe("2 <= 1 ? 'YES' : 'NO'", "Ok('NO')") testEvalToBe("2 <= 1 ? 'YES' : 'NO'", "Ok('NO')")
testEvalToBe("1+1 ? 'YES' : 'NO'", "Error(Expected type: Boolean)") testEvalToBe("1+1 ? 'YES' : 'NO'", "Error(Expected type: Boolean but got: )")
}) })

View File

@ -0,0 +1,80 @@
open Jest
open Expect
open Reducer_TestHelpers
let expectEvalToBeOk = (expr: string) =>
Reducer.evaluate(expr)->Reducer_Helpers.rRemoveDefaultsExternal->E.R.isOk->expect->toBe(true)
let registry = FunctionRegistry_Library.registry
let examples = E.A.to_list(FunctionRegistry_Core.Registry.allExamples(registry))
describe("FunctionRegistry Library", () => {
describe("Regular tests", () => {
testEvalToBe("List.make(3, 'HI')", "Ok(['HI','HI','HI'])")
testEvalToBe("make(3, 'HI')", "Error(Function not found: make(Number,String))")
testEvalToBe("List.upTo(1,3)", "Ok([1,2,3])")
testEvalToBe("List.first([3,5,8])", "Ok(3)")
testEvalToBe("List.last([3,5,8])", "Ok(8)")
testEvalToBe("List.reverse([3,5,8])", "Ok([8,5,3])")
testEvalToBe("Dist.normal(5,2)", "Ok(Normal(5,2))")
testEvalToBe("normal(5,2)", "Ok(Normal(5,2))")
testEvalToBe("normal({mean:5,stdev:2})", "Ok(Normal(5,2))")
testEvalToBe("-2 to 4", "Ok(Normal(1,1.8238704957353074))")
testEvalToBe("pointMass(5)", "Ok(PointMass(5))")
testEvalToBe("Number.floor(5.5)", "Ok(5)")
testEvalToBe("Number.ceil(5.5)", "Ok(6)")
testEvalToBe("floor(5.5)", "Ok(5)")
testEvalToBe("ceil(5.5)", "Ok(6)")
testEvalToBe("Number.abs(5.5)", "Ok(5.5)")
testEvalToBe("abs(5.5)", "Ok(5.5)")
testEvalToBe("Number.exp(10)", "Ok(22026.465794806718)")
testEvalToBe("Number.log10(10)", "Ok(1)")
testEvalToBe("Number.log2(10)", "Ok(3.321928094887362)")
testEvalToBe("Number.sum([2,5,3])", "Ok(10)")
testEvalToBe("sum([2,5,3])", "Ok(10)")
testEvalToBe("Number.product([2,5,3])", "Ok(30)")
testEvalToBe("Number.min([2,5,3])", "Ok(2)")
testEvalToBe("Number.max([2,5,3])", "Ok(5)")
testEvalToBe("Number.mean([0,5,10])", "Ok(5)")
testEvalToBe("Number.geomean([1,5,18])", "Ok(4.481404746557164)")
testEvalToBe("Number.stdev([0,5,10,15])", "Ok(5.5901699437494745)")
testEvalToBe("Number.variance([0,5,10,15])", "Ok(31.25)")
testEvalToBe("Number.sort([10,0,15,5])", "Ok([0,5,10,15])")
testEvalToBe("Number.cumsum([1,5,3])", "Ok([1,6,9])")
testEvalToBe("Number.cumprod([1,5,3])", "Ok([1,5,15])")
testEvalToBe("Number.diff([1,5,3])", "Ok([4,-2])")
testEvalToBe(
"Dist.logScore({estimate: normal(5,2), answer: normal(5.2,1), prior: normal(5.5,3)})",
"Ok(-0.33591375663884876)",
)
testEvalToBe(
"Dist.logScore({estimate: normal(5,2), answer: normal(5.2,1)})",
"Ok(0.32244107041564646)",
)
testEvalToBe("Dist.logScore({estimate: normal(5,2), answer: 4.5})", "Ok(1.6433360626394853)")
testEvalToBe("Dist.klDivergence(normal(5,2), normal(5,1.5))", "Ok(0.06874342818671068)")
})
describe("Fn auto-testing", () => {
testAll("tests of validity", examples, r => {
expectEvalToBeOk(r)
})
testAll(
"tests of type",
E.A.to_list(
FunctionRegistry_Core.Registry.allExamplesWithFns(registry)->E.A2.filter(((fn, _)) =>
E.O.isSome(fn.output)
),
),
((fn, example)) => {
let responseType =
example
->Reducer.evaluate
->E.R2.fmap(ReducerInterface_InternalExpressionValue.externalValueToValueType)
let expectedOutputType = fn.output |> E.O.toExn("")
expect(responseType)->toEqual(Ok(expectedOutputType))
},
)
})
})

View File

@ -117,6 +117,11 @@ let map3 = (
): result<t, sampleSetError> => ): result<t, sampleSetError> =>
E.A.zip3(get(t1), get(t2), get(t3))->E.A2.fmap(E.Tuple3.toFnCall(fn))->_fromSampleResultArray E.A.zip3(get(t1), get(t2), get(t3))->E.A2.fmap(E.Tuple3.toFnCall(fn))->_fromSampleResultArray
let mapN = (~fn: array<float> => result<float, Operation.Error.t>, ~t1: array<t>): result<
t,
sampleSetError,
> => E.A.transpose(E.A.fmap(get, t1))->E.A2.fmap(fn)->_fromSampleResultArray
let mean = t => T.get(t)->E.A.Floats.mean let mean = t => T.get(t)->E.A.Floats.mean
let geomean = t => T.get(t)->E.A.Floats.geomean let geomean = t => T.get(t)->E.A.Floats.geomean
let mode = t => T.get(t)->E.A.Floats.mode let mode = t => T.get(t)->E.A.Floats.mode

View File

@ -1,4 +1,5 @@
type internalExpressionValue = ReducerInterface_InternalExpressionValue.t type internalExpressionValue = ReducerInterface_InternalExpressionValue.t
type internalExpressionValueType = ReducerInterface_InternalExpressionValue.internalExpressionValueType
/* /*
Function Registry "Type". A type, without any other information. Function Registry "Type". A type, without any other information.
@ -42,18 +43,26 @@ and frValueDistOrNumber = FRValueNumber(float) | FRValueDist(DistributionTypes.g
type fnDefinition = { type fnDefinition = {
name: string, name: string,
inputs: array<frType>, inputs: array<frType>,
run: (array<frValue>, GenericDist.env) => result<internalExpressionValue, string>, run: (
array<internalExpressionValue>,
array<frValue>,
GenericDist.env,
) => result<internalExpressionValue, string>,
} }
type function = { type function = {
name: string, name: string,
definitions: array<fnDefinition>, definitions: array<fnDefinition>,
examples: option<string>, requiresNamespace: bool,
nameSpace: string,
output: option<internalExpressionValueType>,
examples: array<string>,
description: option<string>, description: option<string>,
isExperimental: bool, isExperimental: bool,
} }
type registry = array<function> type fnNameDict = Js.Dict.t<array<function>>
type registry = {functions: array<function>, fnNameDict: fnNameDict}
module FRType = { module FRType = {
type t = frType type t = frType
@ -265,7 +274,7 @@ module Matcher = {
module Registry = { module Registry = {
let _findExactMatches = (r: registry, fnName: string, args: array<internalExpressionValue>) => { let _findExactMatches = (r: registry, fnName: string, args: array<internalExpressionValue>) => {
let functionMatchPairs = r->E.A2.fmap(l => (l, Function.match(l, fnName, args))) let functionMatchPairs = r.functions->E.A2.fmap(l => (l, Function.match(l, fnName, args)))
let fullMatch = functionMatchPairs->E.A.getBy(((_, match)) => Match.isFullMatch(match)) let fullMatch = functionMatchPairs->E.A.getBy(((_, match)) => Match.isFullMatch(match))
fullMatch->E.O.bind(((fn, match)) => fullMatch->E.O.bind(((fn, match)) =>
switch match { switch match {
@ -276,7 +285,7 @@ module Matcher = {
} }
let _findNameMatches = (r: registry, fnName: string, args: array<internalExpressionValue>) => { let _findNameMatches = (r: registry, fnName: string, args: array<internalExpressionValue>) => {
let functionMatchPairs = r->E.A2.fmap(l => (l, Function.match(l, fnName, args))) let functionMatchPairs = r.functions->E.A2.fmap(l => (l, Function.match(l, fnName, args)))
let getNameMatches = let getNameMatches =
functionMatchPairs functionMatchPairs
->E.A2.fmap(((fn, match)) => Match.isNameMatchOnly(match) ? Some((fn, match)) : None) ->E.A2.fmap(((fn, match)) => Match.isNameMatchOnly(match) ? Some((fn, match)) : None)
@ -295,10 +304,13 @@ module Matcher = {
} }
let findMatches = (r: registry, fnName: string, args: array<internalExpressionValue>) => { let findMatches = (r: registry, fnName: string, args: array<internalExpressionValue>) => {
switch _findExactMatches(r, fnName, args) { let fnNameInParts = Js.String.split(".", fnName)
let fnToSearch = E.A.get(fnNameInParts, 1) |> E.O.default(fnNameInParts[0])
switch _findExactMatches(r, fnToSearch, args) {
| Some(r) => Match.FullMatch(r) | Some(r) => Match.FullMatch(r)
| None => | None =>
switch _findNameMatches(r, fnName, args) { switch _findNameMatches(r, fnToSearch, args) {
| Some(r) => Match.SameNameDifferentArguments(r) | Some(r) => Match.SameNameDifferentArguments(r)
| None => Match.DifferentName | None => Match.DifferentName
} }
@ -308,7 +320,7 @@ module Matcher = {
let matchToDef = (registry: registry, {fnName, inputIndex}: RegistryMatch.match): option< let matchToDef = (registry: registry, {fnName, inputIndex}: RegistryMatch.match): option<
fnDefinition, fnDefinition,
> => > =>
registry registry.functions
->E.A.getBy(fn => fn.name === fnName) ->E.A.getBy(fn => fn.name === fnName)
->E.O.bind(fn => E.A.get(fn.definitions, inputIndex)) ->E.O.bind(fn => E.A.get(fn.definitions, inputIndex))
} }
@ -322,15 +334,23 @@ module FnDefinition = {
t.name ++ `(${inputs})` t.name ++ `(${inputs})`
} }
let isMatch = (t: t, args: array<internalExpressionValue>) => {
let argValues = FRType.matchWithExpressionValueArray(t.inputs, args)
switch argValues {
| Some(_) => true
| None => false
}
}
let run = (t: t, args: array<internalExpressionValue>, env: GenericDist.env) => { let run = (t: t, args: array<internalExpressionValue>, env: GenericDist.env) => {
let argValues = FRType.matchWithExpressionValueArray(t.inputs, args) let argValues = FRType.matchWithExpressionValueArray(t.inputs, args)
switch argValues { switch argValues {
| Some(values) => t.run(values, env) | Some(values) => t.run(args, values, env)
| None => Error("Incorrect Types") | None => Error("Incorrect Types")
} }
} }
let make = (~name, ~inputs, ~run): t => { let make = (~name, ~inputs, ~run, ()): t => {
name: name, name: name,
inputs: inputs, inputs: inputs,
run: run, run: run,
@ -343,16 +363,29 @@ module Function = {
type functionJson = { type functionJson = {
name: string, name: string,
definitions: array<string>, definitions: array<string>,
examples: option<string>, examples: array<string>,
description: option<string>, description: option<string>,
isExperimental: bool, isExperimental: bool,
} }
let make = (~name, ~definitions, ~examples=?, ~description=?, ~isExperimental=false, ()): t => { let make = (
~name,
~nameSpace,
~requiresNamespace,
~definitions,
~examples=?,
~output=?,
~description=?,
~isExperimental=false,
(),
): t => {
name: name, name: name,
nameSpace: nameSpace,
definitions: definitions, definitions: definitions,
examples: examples, output: output,
examples: examples |> E.O.default([]),
isExperimental: isExperimental, isExperimental: isExperimental,
requiresNamespace: requiresNamespace,
description: description, description: description,
} }
@ -365,22 +398,64 @@ module Function = {
} }
} }
module NameSpace = {
type t = {name: string, functions: array<function>}
let definitions = (t: t) => t.functions->E.A2.fmap(f => f.definitions)->E.A.concatMany
let uniqueFnNames = (t: t) => definitions(t)->E.A2.fmap(r => r.name)->E.A.uniq
let nameToDefinitions = (t: t, name: string) => definitions(t)->E.A2.filter(r => r.name == name)
}
module Registry = { module Registry = {
let toJson = (r: registry) => r->E.A2.fmap(Function.toJson) let toJson = (r: registry) => r.functions->E.A2.fmap(Function.toJson)
let allExamples = (r: registry) => r.functions->E.A2.fmap(r => r.examples)->E.A.concatMany
let allExamplesWithFns = (r: registry) =>
r.functions->E.A2.fmap(fn => fn.examples->E.A2.fmap(example => (fn, example)))->E.A.concatMany
let _buildFnNameDict = (r: array<function>): fnNameDict => {
let allDefinitionsWithFns =
r
->E.A2.fmap(fn => fn.definitions->E.A2.fmap(definitions => (fn, definitions)))
->E.A.concatMany
let functionsWithFnNames =
allDefinitionsWithFns
->E.A2.fmap(((fn, def)) => {
let nameWithNamespace = `${fn.nameSpace}.${def.name}`
let nameWithoutNamespace = def.name
fn.requiresNamespace
? [(nameWithNamespace, fn)]
: [(nameWithNamespace, fn), (nameWithoutNamespace, fn)]
})
->E.A.concatMany
let uniqueNames = functionsWithFnNames->E.A2.fmap(((name, _)) => name)->E.A.uniq
let cacheAsArray: array<(string, array<function>)> = uniqueNames->E.A2.fmap(uniqueName => {
let relevantItems =
E.A2.filter(functionsWithFnNames, ((defName, _)) => defName == uniqueName)->E.A2.fmap(
E.Tuple2.second,
)
(uniqueName, relevantItems)
})
cacheAsArray->Js.Dict.fromArray
}
let make = (fns: array<function>): registry => {
let dict = _buildFnNameDict(fns)
{functions: fns, fnNameDict: dict}
}
/* /*
There's a (potential+minor) bug here: If a function definition is called outside of the calls There's a (potential+minor) bug here: If a function definition is called outside of the calls
to the registry, then it's possible that there could be a match after the registry is to the registry, then it's possible that there could be a match after the registry is
called. However, for now, we could just call the registry last. called. However, for now, we could just call the registry last.
*/ */
let matchAndRun = ( let _matchAndRun = (
~registry: registry, ~registry: registry,
~fnName: string, ~fnName: string,
~args: array<internalExpressionValue>, ~args: array<internalExpressionValue>,
~env: GenericDist.env, ~env: GenericDist.env,
) => { ) => {
let relevantFunctions = Js.Dict.get(registry.fnNameDict, fnName) |> E.O.default([])
let modified = {functions: relevantFunctions, fnNameDict: registry.fnNameDict}
let matchToDef = m => Matcher.Registry.matchToDef(registry, m) let matchToDef = m => Matcher.Registry.matchToDef(registry, m)
//Js.log(toSimple(registry))
let showNameMatchDefinitions = matches => { let showNameMatchDefinitions = matches => {
let defs = let defs =
matches matches
@ -391,10 +466,21 @@ module Registry = {
->E.A2.joinWith("; ") ->E.A2.joinWith("; ")
`There are function matches for ${fnName}(), but with different arguments: ${defs}` `There are function matches for ${fnName}(), but with different arguments: ${defs}`
} }
switch Matcher.Registry.findMatches(registry, fnName, args) {
switch Matcher.Registry.findMatches(modified, fnName, args) {
| Matcher.Match.FullMatch(match) => match->matchToDef->E.O2.fmap(FnDefinition.run(_, args, env)) | Matcher.Match.FullMatch(match) => match->matchToDef->E.O2.fmap(FnDefinition.run(_, args, env))
| SameNameDifferentArguments(m) => Some(Error(showNameMatchDefinitions(m))) | SameNameDifferentArguments(m) => Some(Error(showNameMatchDefinitions(m)))
| _ => None | _ => None
} }
} }
let dispatch = (
registry,
(fnName, args): ReducerInterface_InternalExpressionValue.functionCall,
env,
) => {
_matchAndRun(~registry, ~fnName, ~args, ~env)->E.O2.fmap(
E.R2.errMap(_, s => Reducer_ErrorValue.RETodo(s)),
)
}
} }

View File

@ -213,72 +213,3 @@ module Process = {
twoValues(~fn=Helpers.wrapSymbolic(fn), ~values) twoValues(~fn=Helpers.wrapSymbolic(fn), ~values)
} }
} }
module TwoArgDist = {
let process = (~fn, ~env, r) =>
r
->E.R.bind(Process.DistOrNumberToDist.twoValuesUsingSymbolicDist(~fn, ~values=_, ~env))
->E.R2.fmap(Wrappers.evDistribution)
let make = (name, fn) => {
FnDefinition.make(~name, ~inputs=[FRTypeDistOrNumber, FRTypeDistOrNumber], ~run=(inputs, env) =>
inputs->Prepare.ToValueTuple.twoDistOrNumber->process(~fn, ~env)
)
}
let makeRecordP5P95 = (name, fn) => {
FnDefinition.make(
~name,
~inputs=[FRTypeRecord([("p5", FRTypeDistOrNumber), ("p95", FRTypeDistOrNumber)])],
~run=(inputs, env) => inputs->Prepare.ToValueTuple.Record.twoDistOrNumber->process(~fn, ~env),
)
}
let makeRecordMeanStdev = (name, fn) => {
FnDefinition.make(
~name,
~inputs=[FRTypeRecord([("mean", FRTypeDistOrNumber), ("stdev", FRTypeDistOrNumber)])],
~run=(inputs, env) => inputs->Prepare.ToValueTuple.Record.twoDistOrNumber->process(~fn, ~env),
)
}
}
module OneArgDist = {
let process = (~fn, ~env, r) =>
r
->E.R.bind(Process.DistOrNumberToDist.oneValueUsingSymbolicDist(~fn, ~value=_, ~env))
->E.R2.fmap(Wrappers.evDistribution)
let make = (name, fn) =>
FnDefinition.make(~name, ~inputs=[FRTypeDistOrNumber], ~run=(inputs, env) =>
inputs->Prepare.ToValueTuple.oneDistOrNumber->process(~fn, ~env)
)
}
module ArrayNumberDist = {
let make = (name, fn) => {
FnDefinition.make(~name, ~inputs=[FRTypeArray(FRTypeNumber)], ~run=(inputs, _) =>
Prepare.ToTypedArray.numbers(inputs)
->E.R.bind(r => E.A.length(r) === 0 ? Error("List is empty") : Ok(r))
->E.R.bind(fn)
)
}
let make2 = (name, fn) => {
FnDefinition.make(~name, ~inputs=[FRTypeArray(FRTypeAny)], ~run=(inputs, _) =>
Prepare.ToTypedArray.numbers(inputs)
->E.R.bind(r => E.A.length(r) === 0 ? Error("List is empty") : Ok(r))
->E.R.bind(fn)
)
}
}
module NumberToNumber = {
let make = (name, fn) =>
FnDefinition.make(~name, ~inputs=[FRTypeNumber], ~run=(inputs, _) => {
inputs
->getOrError(0)
->E.R.bind(Prepare.oneNumber)
->E.R2.fmap(fn)
->E.R2.fmap(Wrappers.evNumber)
})
}

View File

@ -1,576 +1,12 @@
open FunctionRegistry_Core let fnList = Belt.Array.concatMany([
open FunctionRegistry_Helpers FR_Dict.library,
FR_Dist.library,
FR_Fn.library,
FR_List.library,
FR_Number.library,
FR_Pointset.library,
FR_Scoring.library,
])
let twoArgs = E.Tuple2.toFnCall let registry = FunctionRegistry_Core.Registry.make(fnList)
let dispatch = FunctionRegistry_Core.Registry.dispatch(registry)
module Declaration = {
let frType = FRTypeRecord([
("fn", FRTypeLambda),
("inputs", FRTypeArray(FRTypeRecord([("min", FRTypeNumber), ("max", FRTypeNumber)]))),
])
let fromExpressionValue = (e: frValue): result<internalExpressionValue, string> => {
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.twoArgs([e]) {
| Ok([FRValueLambda(lambda), FRValueArray(inputs)]) => {
open FunctionRegistry_Helpers.Prepare
let getMinMax = arg =>
ToValueArray.Record.toArgs([arg])
->E.R.bind(ToValueTuple.twoNumbers)
->E.R2.fmap(((min, max)) => Declaration.ContinuousFloatArg.make(min, max))
inputs
->E.A2.fmap(getMinMax)
->E.A.R.firstErrorOrOpen
->E.R2.fmap(args => ReducerInterface_InternalExpressionValue.IEvDeclaration(
Declaration.make(lambda, args),
))
}
| Error(r) => Error(r)
| Ok(_) => Error(FunctionRegistry_Helpers.impossibleError)
}
}
}
let inputsTodist = (inputs: array<FunctionRegistry_Core.frValue>, makeDist) => {
let array = inputs->getOrError(0)->E.R.bind(Prepare.ToValueArray.Array.openA)
let xyCoords =
array->E.R.bind(xyCoords =>
xyCoords
->E.A2.fmap(xyCoord =>
[xyCoord]->Prepare.ToValueArray.Record.twoArgs->E.R.bind(Prepare.ToValueTuple.twoNumbers)
)
->E.A.R.firstErrorOrOpen
)
let expressionValue =
xyCoords
->E.R.bind(r => r->XYShape.T.makeFromZipped->E.R2.errMap(XYShape.Error.toString))
->E.R2.fmap(r => ReducerInterface_InternalExpressionValue.IEvDistribution(
PointSet(makeDist(r)),
))
expressionValue
}
let registryStart = [
Function.make(
~name="toContinuousPointSet",
~definitions=[
FnDefinition.make(
~name="toContinuousPointSet",
~inputs=[FRTypeArray(FRTypeRecord([("x", FRTypeNumeric), ("y", FRTypeNumeric)]))],
~run=(inputs, _) => inputsTodist(inputs, r => Continuous(Continuous.make(r))),
),
],
(),
),
Function.make(
~name="toDiscretePointSet",
~definitions=[
FnDefinition.make(
~name="toDiscretePointSet",
~inputs=[FRTypeArray(FRTypeRecord([("x", FRTypeNumeric), ("y", FRTypeNumeric)]))],
~run=(inputs, _) => inputsTodist(inputs, r => Discrete(Discrete.make(r))),
),
],
(),
),
Function.make(
~name="Declaration",
~definitions=[
FnDefinition.make(~name="declareFn", ~inputs=[Declaration.frType], ~run=(inputs, _) => {
inputs->getOrError(0)->E.R.bind(Declaration.fromExpressionValue)
}),
],
(),
),
Function.make(
~name="Normal",
~examples=`normal(5,1)
normal({p5: 4, p95: 10})
normal({mean: 5, stdev: 2})`,
~definitions=[
TwoArgDist.make("normal", twoArgs(SymbolicDist.Normal.make)),
TwoArgDist.makeRecordP5P95("normal", r =>
twoArgs(SymbolicDist.Normal.from90PercentCI, r)->Ok
),
TwoArgDist.makeRecordMeanStdev("normal", twoArgs(SymbolicDist.Normal.make)),
],
(),
),
Function.make(
~name="Lognormal",
~examples=`lognormal(0.5, 0.8)
lognormal({p5: 4, p95: 10})
lognormal({mean: 5, stdev: 2})`,
~definitions=[
TwoArgDist.make("lognormal", twoArgs(SymbolicDist.Lognormal.make)),
TwoArgDist.makeRecordP5P95("lognormal", r =>
twoArgs(SymbolicDist.Lognormal.from90PercentCI, r)->Ok
),
TwoArgDist.makeRecordMeanStdev("lognormal", twoArgs(SymbolicDist.Lognormal.fromMeanAndStdev)),
],
(),
),
Function.make(
~name="Uniform",
~examples=`uniform(10, 12)`,
~definitions=[TwoArgDist.make("uniform", twoArgs(SymbolicDist.Uniform.make))],
(),
),
Function.make(
~name="Beta",
~examples=`beta(20, 25)
beta({mean: 0.39, stdev: 0.1})`,
~definitions=[
TwoArgDist.make("beta", twoArgs(SymbolicDist.Beta.make)),
TwoArgDist.makeRecordMeanStdev("beta", twoArgs(SymbolicDist.Beta.fromMeanAndStdev)),
],
(),
),
Function.make(
~name="Cauchy",
~examples=`cauchy(5, 1)`,
~definitions=[TwoArgDist.make("cauchy", twoArgs(SymbolicDist.Cauchy.make))],
(),
),
Function.make(
~name="Gamma",
~examples=`gamma(5, 1)`,
~definitions=[TwoArgDist.make("gamma", twoArgs(SymbolicDist.Gamma.make))],
(),
),
Function.make(
~name="Logistic",
~examples=`gamma(5, 1)`,
~definitions=[TwoArgDist.make("logistic", twoArgs(SymbolicDist.Logistic.make))],
(),
),
Function.make(
~name="To (Distribution)",
~examples=`5 to 10
to(5,10)
-5 to 5`,
~definitions=[
TwoArgDist.make("to", twoArgs(SymbolicDist.From90thPercentile.make)),
TwoArgDist.make(
"credibleIntervalToDistribution",
twoArgs(SymbolicDist.From90thPercentile.make),
),
],
(),
),
Function.make(
~name="Exponential",
~examples=`exponential(2)`,
~definitions=[OneArgDist.make("exponential", SymbolicDist.Exponential.make)],
(),
),
Function.make(
~name="Bernoulli",
~examples=`bernoulli(0.5)`,
~definitions=[OneArgDist.make("bernoulli", SymbolicDist.Bernoulli.make)],
(),
),
Function.make(
~name="PointMass",
~examples=`pointMass(0.5)`,
~definitions=[OneArgDist.make("pointMass", SymbolicDist.Float.makeSafe)],
(),
),
Function.make(
~name="toContinuousPointSet",
~description="Converts a set of points to a continuous distribution",
~examples=`toContinuousPointSet([
{x: 0, y: 0.1},
{x: 1, y: 0.2},
{x: 2, y: 0.15},
{x: 3, y: 0.1}
])`,
~definitions=[
FnDefinition.make(
~name="toContinuousPointSet",
~inputs=[FRTypeArray(FRTypeRecord([("x", FRTypeNumeric), ("y", FRTypeNumeric)]))],
~run=(inputs, _) => inputsTodist(inputs, r => Continuous(Continuous.make(r))),
),
],
(),
),
Function.make(
~name="toDiscretePointSet",
~description="Converts a set of points to a discrete distribution",
~examples=`toDiscretePointSet([
{x: 0, y: 0.1},
{x: 1, y: 0.2},
{x: 2, y: 0.15},
{x: 3, y: 0.1}
])`,
~definitions=[
FnDefinition.make(
~name="toDiscretePointSet",
~inputs=[FRTypeArray(FRTypeRecord([("x", FRTypeNumeric), ("y", FRTypeNumeric)]))],
~run=(inputs, _) => inputsTodist(inputs, r => Discrete(Discrete.make(r))),
),
],
(),
),
Function.make(
~name="Declaration (Continuous Function)",
~description="Adds metadata to a function of the input ranges. Works now for numeric and date inputs. This is useful when making predictions. It allows you to limit the domain that your prediction will be used and scored within.",
~examples=`declareFn({
fn: {|a,b| a },
inputs: [
{min: 0, max: 100},
{min: 30, max: 50}
]
})`,
~definitions=[
FnDefinition.make(~name="declareFn", ~inputs=[Declaration.frType], ~run=(inputs, _) => {
inputs->E.A.unsafe_get(0)->Declaration.fromExpressionValue
}),
],
~isExperimental=true,
(),
),
Function.make(
~name="Floor",
~definitions=[NumberToNumber.make("floor", Js.Math.floor_float)],
(),
),
Function.make(
~name="Ceiling",
~definitions=[NumberToNumber.make("ceil", Js.Math.ceil_float)],
(),
),
Function.make(
~name="Absolute Value",
~definitions=[NumberToNumber.make("abs", Js.Math.abs_float)],
(),
),
Function.make(~name="Exponent", ~definitions=[NumberToNumber.make("exp", Js.Math.exp)], ()),
Function.make(~name="Log", ~definitions=[NumberToNumber.make("log", Js.Math.log)], ()),
Function.make(
~name="Log Base 10",
~definitions=[NumberToNumber.make("log10", Js.Math.log10)],
(),
),
Function.make(~name="Log Base 2", ~definitions=[NumberToNumber.make("log2", Js.Math.log2)], ()),
Function.make(~name="Round", ~definitions=[NumberToNumber.make("round", Js.Math.round)], ()),
Function.make(
~name="Sum",
~definitions=[ArrayNumberDist.make("sum", r => r->E.A.Floats.sum->Wrappers.evNumber->Ok)],
(),
),
Function.make(
~name="Product",
~definitions=[
ArrayNumberDist.make("product", r => r->E.A.Floats.product->Wrappers.evNumber->Ok),
],
(),
),
Function.make(
~name="Min",
~definitions=[ArrayNumberDist.make("min", r => r->E.A.Floats.min->Wrappers.evNumber->Ok)],
(),
),
Function.make(
~name="Max",
~definitions=[ArrayNumberDist.make("max", r => r->E.A.Floats.max->Wrappers.evNumber->Ok)],
(),
),
Function.make(
~name="Mean",
~definitions=[ArrayNumberDist.make("mean", r => r->E.A.Floats.mean->Wrappers.evNumber->Ok)],
(),
),
Function.make(
~name="Geometric Mean",
~definitions=[
ArrayNumberDist.make("geomean", r => r->E.A.Floats.geomean->Wrappers.evNumber->Ok),
],
(),
),
Function.make(
~name="Standard Deviation",
~definitions=[ArrayNumberDist.make("stdev", r => r->E.A.Floats.stdev->Wrappers.evNumber->Ok)],
(),
),
Function.make(
~name="Variance",
~definitions=[
ArrayNumberDist.make("variance", r => r->E.A.Floats.stdev->Wrappers.evNumber->Ok),
],
(),
),
Function.make(
~name="First",
~definitions=[
ArrayNumberDist.make2("first", r =>
r->E.A.first |> E.O.toResult(impossibleError) |> E.R.fmap(Wrappers.evNumber)
),
],
(),
),
Function.make(
~name="Last",
~definitions=[
ArrayNumberDist.make2("last", r =>
r->E.A.last |> E.O.toResult(impossibleError) |> E.R.fmap(Wrappers.evNumber)
),
],
(),
),
Function.make(
~name="Sort",
~definitions=[
ArrayNumberDist.make("sort", r =>
r->E.A.Floats.sort->E.A2.fmap(Wrappers.evNumber)->Wrappers.evArray->Ok
),
],
(),
),
Function.make(
~name="Reverse",
~definitions=[
ArrayNumberDist.make("reverse", r =>
r->Belt_Array.reverse->E.A2.fmap(Wrappers.evNumber)->Wrappers.evArray->Ok
),
],
(),
),
Function.make(
~name="Cumulative Sum",
~definitions=[
ArrayNumberDist.make("cumsum", r =>
r->E.A.Floats.cumsum->E.A2.fmap(Wrappers.evNumber)->Wrappers.evArray->Ok
),
],
(),
),
Function.make(
~name="Cumulative Prod",
~definitions=[
ArrayNumberDist.make("cumprod", r =>
r->E.A.Floats.cumsum->E.A2.fmap(Wrappers.evNumber)->Wrappers.evArray->Ok
),
],
(),
),
Function.make(
~name="Diff",
~definitions=[
ArrayNumberDist.make("diff", r =>
r->E.A.Floats.diff->E.A2.fmap(Wrappers.evNumber)->Wrappers.evArray->Ok
),
],
(),
),
Function.make(
~name="Dict.merge",
~definitions=[
FnDefinition.make(
~name="merge",
~inputs=[FRTypeDict(FRTypeAny), FRTypeDict(FRTypeAny)],
~run=(inputs, _) => {
switch inputs {
| [FRValueDict(d1), FRValueDict(d2)] => {
let newDict =
E.Dict.concat(d1, d2) |> Js.Dict.map((. r) =>
FunctionRegistry_Core.FRType.matchReverse(r)
)
newDict->Js.Dict.entries->Belt.Map.String.fromArray->Wrappers.evRecord->Ok
}
| _ => Error(impossibleError)
}
},
),
],
(),
),
//TODO: Make sure that two functions can't have the same name. This causes chaos elsewhere.
Function.make(
~name="Dict.mergeMany",
~definitions=[
FnDefinition.make(~name="mergeMany", ~inputs=[FRTypeArray(FRTypeDict(FRTypeAny))], ~run=(
inputs,
_,
) =>
inputs
->Prepare.ToTypedArray.dicts
->E.R2.fmap(E.Dict.concatMany)
->E.R2.fmap(Js.Dict.map((. r) => FunctionRegistry_Core.FRType.matchReverse(r)))
->E.R2.fmap(r => r->Js.Dict.entries->Belt.Map.String.fromArray)
->E.R2.fmap(Wrappers.evRecord)
),
],
(),
),
Function.make(
~name="Dict.keys",
~definitions=[
FnDefinition.make(~name="keys", ~inputs=[FRTypeDict(FRTypeAny)], ~run=(inputs, _) =>
switch inputs {
| [FRValueDict(d1)] => Js.Dict.keys(d1)->E.A2.fmap(Wrappers.evString)->Wrappers.evArray->Ok
| _ => Error(impossibleError)
}
),
],
(),
),
Function.make(
~name="Dict.values",
~definitions=[
FnDefinition.make(~name="values", ~inputs=[FRTypeDict(FRTypeAny)], ~run=(inputs, _) =>
switch inputs {
| [FRValueDict(d1)] =>
Js.Dict.values(d1)
->E.A2.fmap(FunctionRegistry_Core.FRType.matchReverse)
->Wrappers.evArray
->Ok
| _ => Error(impossibleError)
}
),
],
(),
),
Function.make(
~name="Dict.toList",
~definitions=[
FnDefinition.make(~name="dictToList", ~inputs=[FRTypeDict(FRTypeAny)], ~run=(inputs, _) =>
switch inputs {
| [FRValueDict(dict)] =>
dict
->Js.Dict.entries
->E.A2.fmap(((key, value)) =>
Wrappers.evArray([
Wrappers.evString(key),
FunctionRegistry_Core.FRType.matchReverse(value),
])
)
->Wrappers.evArray
->Ok
| _ => Error(impossibleError)
}
),
],
(),
),
Function.make(
~name="Dict.fromList",
~definitions=[
FnDefinition.make(~name="dictFromList", ~inputs=[FRTypeArray(FRTypeArray(FRTypeAny))], ~run=(
inputs,
_,
) => {
let convertInternalItems = items =>
items
->E.A2.fmap(item => {
switch item {
| [FRValueString(string), value] =>
(string, FunctionRegistry_Core.FRType.matchReverse(value))->Ok
| _ => Error(impossibleError)
}
})
->E.A.R.firstErrorOrOpen
->E.R2.fmap(Belt.Map.String.fromArray)
->E.R2.fmap(Wrappers.evRecord)
inputs->getOrError(0)->E.R.bind(Prepare.ToValueArray.Array.arrayOfArrays)
|> E.R2.bind(convertInternalItems)
}),
],
(),
),
Function.make(
~name="List.make",
~definitions=[
//Todo: If the second item is a function with no args, it could be nice to run this function and return the result.
FnDefinition.make(~name="listMake", ~inputs=[FRTypeNumber, FRTypeAny], ~run=(inputs, _) => {
switch inputs {
| [FRValueNumber(number), value] =>
Belt.Array.make(E.Float.toInt(number), value)
->E.A2.fmap(FunctionRegistry_Core.FRType.matchReverse)
->Wrappers.evArray
->Ok
| _ => Error(impossibleError)
}
}),
],
(),
),
Function.make(
~name="upTo",
~definitions=[
FnDefinition.make(~name="upTo", ~inputs=[FRTypeNumber, FRTypeNumber], ~run=(inputs, _) =>
inputs
->Prepare.ToValueTuple.twoNumbers
->E.R2.fmap(((low, high)) =>
E.A.Floats.range(low, high, (high -. low +. 1.0)->E.Float.toInt)
->E.A2.fmap(Wrappers.evNumber)
->Wrappers.evArray
)
),
],
(),
),
]
let runScoring = (estimate, answer, prior, env) => {
GenericDist.Score.logScore(~estimate, ~answer, ~prior, ~env)
->E.R2.fmap(FunctionRegistry_Helpers.Wrappers.evNumber)
->E.R2.errMap(DistributionTypes.Error.toString)
}
let scoreFunctions = [
Function.make(
~name="Score",
~definitions=[
FnDefinition.make(
~name="logScore",
~inputs=[
FRTypeRecord([
("estimate", FRTypeDist),
("answer", FRTypeDistOrNumber),
("prior", FRTypeDist),
]),
],
~run=(inputs, env) => {
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.threeArgs(inputs) {
| Ok([FRValueDist(estimate), FRValueDistOrNumber(FRValueDist(d)), FRValueDist(prior)]) =>
runScoring(estimate, Score_Dist(d), Some(prior), env)
| Ok([
FRValueDist(estimate),
FRValueDistOrNumber(FRValueNumber(d)),
FRValueDist(prior),
]) =>
runScoring(estimate, Score_Scalar(d), Some(prior), env)
| Error(e) => Error(e)
| _ => Error(FunctionRegistry_Helpers.impossibleError)
}
},
),
FnDefinition.make(
~name="logScore",
~inputs=[FRTypeRecord([("estimate", FRTypeDist), ("answer", FRTypeDistOrNumber)])],
~run=(inputs, env) => {
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.twoArgs(inputs) {
| Ok([FRValueDist(estimate), FRValueDistOrNumber(FRValueDist(d))]) =>
runScoring(estimate, Score_Dist(d), None, env)
| Ok([FRValueDist(estimate), FRValueDistOrNumber(FRValueNumber(d))]) =>
runScoring(estimate, Score_Scalar(d), None, env)
| Error(e) => Error(e)
| _ => Error(FunctionRegistry_Helpers.impossibleError)
}
},
),
FnDefinition.make(~name="klDivergence", ~inputs=[FRTypeDist, FRTypeDist], ~run=(
inputs,
env,
) => {
switch inputs {
| [FRValueDist(estimate), FRValueDist(d)] => runScoring(estimate, Score_Dist(d), None, env)
| _ => Error(FunctionRegistry_Helpers.impossibleError)
}
}),
],
(),
),
]
let registry = E.A.append(registryStart, scoreFunctions)

View File

@ -0,0 +1,169 @@
open FunctionRegistry_Core
open FunctionRegistry_Helpers
let nameSpace = "Dict"
module Internals = {
type t = ReducerInterface_InternalExpressionValue.map
let keys = (a: t): internalExpressionValue => IEvArray(
Belt.Map.String.keysToArray(a)->E.A2.fmap(Wrappers.evString),
)
let values = (a: t): internalExpressionValue => IEvArray(Belt.Map.String.valuesToArray(a))
let toList = (a: t): internalExpressionValue =>
Belt.Map.String.toArray(a)
->E.A2.fmap(((key, value)) => Wrappers.evArray([IEvString(key), value]))
->Wrappers.evArray
let fromList = (items: array<internalExpressionValue>): result<internalExpressionValue, string> =>
items
->E.A2.fmap(item => {
switch (item: internalExpressionValue) {
| IEvArray([IEvString(string), value]) => (string, value)->Ok
| _ => Error(impossibleError)
}
})
->E.A.R.firstErrorOrOpen
->E.R2.fmap(Belt.Map.String.fromArray)
->E.R2.fmap(Wrappers.evRecord)
let merge = (a: t, b: t): internalExpressionValue => IEvRecord(
Belt.Map.String.merge(a, b, (_, _, c) => c),
)
//Belt.Map.String has a function for mergeMany, but I couldn't understand how to use it yet.
let mergeMany = (a: array<t>): internalExpressionValue => {
let mergedValues =
a->E.A2.fmap(Belt.Map.String.toArray)->Belt.Array.concatMany->Belt.Map.String.fromArray
IEvRecord(mergedValues)
}
}
let library = [
Function.make(
~name="merge",
~nameSpace,
~requiresNamespace=true,
~output=EvtRecord,
~examples=[`Dict.merge({a: 1, b: 2}, {c: 3, d: 4})`],
~definitions=[
FnDefinition.make(
~name="merge",
~inputs=[FRTypeDict(FRTypeAny), FRTypeDict(FRTypeAny)],
~run=(inputs, _, _) => {
switch inputs {
| [IEvRecord(d1), IEvRecord(d2)] => Internals.merge(d1, d2)->Ok
| _ => Error(impossibleError)
}
},
(),
),
],
(),
),
//TODO: Change to use new mergeMany() function.
Function.make(
~name="mergeMany",
~nameSpace,
~requiresNamespace=true,
~output=EvtRecord,
~examples=[`Dict.mergeMany([{a: 1, b: 2}, {c: 3, d: 4}])`],
~definitions=[
FnDefinition.make(
~name="mergeMany",
~inputs=[FRTypeArray(FRTypeDict(FRTypeAny))],
~run=(_, inputs, _) =>
inputs
->Prepare.ToTypedArray.dicts
->E.R2.fmap(E.Dict.concatMany)
->E.R2.fmap(Js.Dict.map((. r) => FunctionRegistry_Core.FRType.matchReverse(r)))
->E.R2.fmap(r => r->Js.Dict.entries->Belt.Map.String.fromArray)
->E.R2.fmap(Wrappers.evRecord),
(),
),
],
(),
),
Function.make(
~name="keys",
~nameSpace,
~requiresNamespace=true,
~output=EvtArray,
~examples=[`Dict.keys({a: 1, b: 2})`],
~definitions=[
FnDefinition.make(
~name="keys",
~inputs=[FRTypeDict(FRTypeAny)],
~run=(inputs, _, _) =>
switch inputs {
| [IEvRecord(d1)] => Internals.keys(d1)->Ok
| _ => Error(impossibleError)
},
(),
),
],
(),
),
Function.make(
~name="values",
~nameSpace,
~requiresNamespace=true,
~output=EvtArray,
~examples=[`Dict.values({a: 1, b: 2})`],
~definitions=[
FnDefinition.make(
~name="values",
~inputs=[FRTypeDict(FRTypeAny)],
~run=(inputs, _, _) =>
switch inputs {
| [IEvRecord(d1)] => Internals.values(d1)->Ok
| _ => Error(impossibleError)
},
(),
),
],
(),
),
Function.make(
~name="toList",
~nameSpace,
~requiresNamespace=true,
~output=EvtArray,
~examples=[`Dict.toList({a: 1, b: 2})`],
~definitions=[
FnDefinition.make(
~name="toList",
~inputs=[FRTypeDict(FRTypeAny)],
~run=(inputs, _, _) =>
switch inputs {
| [IEvRecord(dict)] => dict->Internals.toList->Ok
| _ => Error(impossibleError)
},
(),
),
],
(),
),
Function.make(
~name="fromList",
~nameSpace,
~requiresNamespace=true,
~output=EvtRecord,
~examples=[`Dict.fromList([["a", 1], ["b", 2]])`],
~definitions=[
FnDefinition.make(
~name="fromList",
~inputs=[FRTypeArray(FRTypeArray(FRTypeAny))],
~run=(inputs, _, _) =>
switch inputs {
| [IEvArray(items)] => Internals.fromList(items)
| _ => Error(impossibleError)
},
(),
),
],
(),
),
]

View File

@ -0,0 +1,152 @@
open FunctionRegistry_Core
open FunctionRegistry_Helpers
let twoArgs = E.Tuple2.toFnCall
module DistributionCreation = {
let nameSpace = "Dist"
let output = ReducerInterface_InternalExpressionValue.EvtDistribution
let requiresNamespace = false
let fnMake = (~name, ~examples, ~definitions) => {
Function.make(~name, ~nameSpace, ~output, ~examples, ~definitions, ~requiresNamespace, ())
}
module TwoArgDist = {
let process = (~fn, ~env, r) =>
r
->E.R.bind(Process.DistOrNumberToDist.twoValuesUsingSymbolicDist(~fn, ~values=_, ~env))
->E.R2.fmap(Wrappers.evDistribution)
let make = (name, fn) => {
FnDefinition.make(
~name,
~inputs=[FRTypeDistOrNumber, FRTypeDistOrNumber],
~run=(_, inputs, env) => inputs->Prepare.ToValueTuple.twoDistOrNumber->process(~fn, ~env),
(),
)
}
let makeRecordP5P95 = (name, fn) => {
FnDefinition.make(
~name,
~inputs=[FRTypeRecord([("p5", FRTypeDistOrNumber), ("p95", FRTypeDistOrNumber)])],
~run=(_, inputs, env) =>
inputs->Prepare.ToValueTuple.Record.twoDistOrNumber->process(~fn, ~env),
(),
)
}
let makeRecordMeanStdev = (name, fn) => {
FnDefinition.make(
~name,
~inputs=[FRTypeRecord([("mean", FRTypeDistOrNumber), ("stdev", FRTypeDistOrNumber)])],
~run=(_, inputs, env) =>
inputs->Prepare.ToValueTuple.Record.twoDistOrNumber->process(~fn, ~env),
(),
)
}
}
module OneArgDist = {
let process = (~fn, ~env, r) =>
r
->E.R.bind(Process.DistOrNumberToDist.oneValueUsingSymbolicDist(~fn, ~value=_, ~env))
->E.R2.fmap(Wrappers.evDistribution)
let make = (name, fn) =>
FnDefinition.make(
~name,
~inputs=[FRTypeDistOrNumber],
~run=(_, inputs, env) => inputs->Prepare.ToValueTuple.oneDistOrNumber->process(~fn, ~env),
(),
)
}
let library = [
fnMake(
~name="normal",
~examples=["normal(5,1)", "normal({p5: 4, p95: 10})", "normal({mean: 5, stdev: 2})"],
~definitions=[
TwoArgDist.make("normal", twoArgs(SymbolicDist.Normal.make)),
TwoArgDist.makeRecordP5P95("normal", r =>
twoArgs(SymbolicDist.Normal.from90PercentCI, r)->Ok
),
TwoArgDist.makeRecordMeanStdev("normal", twoArgs(SymbolicDist.Normal.make)),
],
),
fnMake(
~name="lognormal",
~examples=[
"lognormal(0.5, 0.8)",
"lognormal({p5: 4, p95: 10})",
"lognormal({mean: 5, stdev: 2})",
],
~definitions=[
TwoArgDist.make("lognormal", twoArgs(SymbolicDist.Lognormal.make)),
TwoArgDist.makeRecordP5P95("lognormal", r =>
twoArgs(SymbolicDist.Lognormal.from90PercentCI, r)->Ok
),
TwoArgDist.makeRecordMeanStdev(
"lognormal",
twoArgs(SymbolicDist.Lognormal.fromMeanAndStdev),
),
],
),
fnMake(
~name="uniform",
~examples=[`uniform(10, 12)`],
~definitions=[TwoArgDist.make("uniform", twoArgs(SymbolicDist.Uniform.make))],
),
fnMake(
~name="beta",
~examples=[`beta(20, 25)`, `beta({mean: 0.39, stdev: 0.1})`],
~definitions=[
TwoArgDist.make("beta", twoArgs(SymbolicDist.Beta.make)),
TwoArgDist.makeRecordMeanStdev("beta", twoArgs(SymbolicDist.Beta.fromMeanAndStdev)),
],
),
fnMake(
~name="cauchy",
~examples=[`cauchy(5, 1)`],
~definitions=[TwoArgDist.make("cauchy", twoArgs(SymbolicDist.Cauchy.make))],
),
fnMake(
~name="gamma",
~examples=[`gamma(5, 1)`],
~definitions=[TwoArgDist.make("gamma", twoArgs(SymbolicDist.Gamma.make))],
),
fnMake(
~name="logistic",
~examples=[`logistic(5, 1)`],
~definitions=[TwoArgDist.make("logistic", twoArgs(SymbolicDist.Logistic.make))],
),
fnMake(
~name="to (distribution)",
~examples=[`5 to 10`, `to(5,10)`, `-5 to 5`],
~definitions=[
TwoArgDist.make("to", twoArgs(SymbolicDist.From90thPercentile.make)),
TwoArgDist.make(
"credibleIntervalToDistribution",
twoArgs(SymbolicDist.From90thPercentile.make),
),
],
),
fnMake(
~name="exponential",
~examples=[`exponential(2)`],
~definitions=[OneArgDist.make("exponential", SymbolicDist.Exponential.make)],
),
fnMake(
~name="bernoulli",
~examples=[`bernoulli(0.5)`],
~definitions=[OneArgDist.make("bernoulli", SymbolicDist.Bernoulli.make)],
),
fnMake(
~name="pointMass",
~examples=[`pointMass(0.5)`],
~definitions=[OneArgDist.make("pointMass", SymbolicDist.Float.makeSafe)],
),
]
}
let library = DistributionCreation.library

View File

@ -0,0 +1,62 @@
open FunctionRegistry_Core
open FunctionRegistry_Helpers
module Declaration = {
let frType = FRTypeRecord([
("fn", FRTypeLambda),
("inputs", FRTypeArray(FRTypeRecord([("min", FRTypeNumber), ("max", FRTypeNumber)]))),
])
let fromExpressionValue = (e: frValue): result<internalExpressionValue, string> => {
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.twoArgs([e]) {
| Ok([FRValueLambda(lambda), FRValueArray(inputs)]) => {
open FunctionRegistry_Helpers.Prepare
let getMinMax = arg =>
ToValueArray.Record.toArgs([arg])
->E.R.bind(ToValueTuple.twoNumbers)
->E.R2.fmap(((min, max)) => Declaration.ContinuousFloatArg.make(min, max))
inputs
->E.A2.fmap(getMinMax)
->E.A.R.firstErrorOrOpen
->E.R2.fmap(args => ReducerInterface_InternalExpressionValue.IEvDeclaration(
Declaration.make(lambda, args),
))
}
| Error(r) => Error(r)
| Ok(_) => Error(FunctionRegistry_Helpers.impossibleError)
}
}
}
let nameSpace = "Function"
let library = [
Function.make(
~name="declare",
~nameSpace,
~requiresNamespace=true,
~output=EvtDeclaration,
~description="Adds metadata to a function of the input ranges. Works now for numeric and date inputs. This is useful when making predictions. It allows you to limit the domain that your prediction will be used and scored within.",
~examples=[
`Function.declare({
fn: {|a,b| a },
inputs: [
{min: 0, max: 100},
{min: 30, max: 50}
]
})`,
],
~isExperimental=true,
~definitions=[
FnDefinition.make(
~name="declare",
~inputs=[Declaration.frType],
~run=(_, inputs, _) => {
inputs->getOrError(0)->E.R.bind(Declaration.fromExpressionValue)
},
(),
),
],
(),
),
]

View File

@ -0,0 +1,128 @@
open FunctionRegistry_Core
open FunctionRegistry_Helpers
let nameSpace = "List"
let requiresNamespace = true
module Internals = {
let makeFromNumber = (
n: float,
value: internalExpressionValue,
): internalExpressionValue => IEvArray(Belt.Array.make(E.Float.toInt(n), value))
let upTo = (low: float, high: float): internalExpressionValue => IEvArray(
E.A.Floats.range(low, high, (high -. low +. 1.0)->E.Float.toInt)->E.A2.fmap(Wrappers.evNumber),
)
let first = (v: array<internalExpressionValue>): result<internalExpressionValue, string> =>
v->E.A.first |> E.O.toResult("No first element")
let last = (v: array<internalExpressionValue>): result<internalExpressionValue, string> =>
v->E.A.last |> E.O.toResult("No last element")
let reverse = (array: array<internalExpressionValue>): internalExpressionValue => IEvArray(
Belt.Array.reverse(array),
)
}
let library = [
Function.make(
~name="make",
~nameSpace,
~requiresNamespace,
~output=EvtArray,
~examples=[`List.make(2, "testValue")`],
~definitions=[
//Todo: If the second item is a function with no args, it could be nice to run this function and return the result.
FnDefinition.make(
~name="make",
~inputs=[FRTypeNumber, FRTypeAny],
~run=(inputs, _, _) => {
switch inputs {
| [IEvNumber(number), value] => Internals.makeFromNumber(number, value)->Ok
| _ => Error(impossibleError)
}
},
(),
),
],
(),
),
Function.make(
~name="upTo",
~nameSpace,
~requiresNamespace,
~output=EvtArray,
~examples=[`List.upTo(1,4)`],
~definitions=[
FnDefinition.make(
~name="upTo",
~inputs=[FRTypeNumber, FRTypeNumber],
~run=(_, inputs, _) =>
inputs
->Prepare.ToValueTuple.twoNumbers
->E.R2.fmap(((low, high)) => Internals.upTo(low, high)),
(),
),
],
(),
),
Function.make(
~name="first",
~nameSpace,
~requiresNamespace,
~examples=[`List.first([1,4,5])`],
~definitions=[
FnDefinition.make(
~name="first",
~inputs=[FRTypeArray(FRTypeAny)],
~run=(inputs, _, _) =>
switch inputs {
| [IEvArray(array)] => Internals.first(array)
| _ => Error(impossibleError)
},
(),
),
],
(),
),
Function.make(
~name="last",
~nameSpace,
~requiresNamespace,
~examples=[`List.last([1,4,5])`],
~definitions=[
FnDefinition.make(
~name="last",
~inputs=[FRTypeArray(FRTypeAny)],
~run=(inputs, _, _) =>
switch inputs {
| [IEvArray(array)] => Internals.last(array)
| _ => Error(impossibleError)
},
(),
),
],
(),
),
Function.make(
~name="reverse",
~nameSpace,
~output=EvtArray,
~requiresNamespace=false,
~examples=[`List.reverse([1,4,5])`],
~definitions=[
FnDefinition.make(
~name="reverse",
~inputs=[FRTypeArray(FRTypeAny)],
~run=(inputs, _, _) =>
switch inputs {
| [IEvArray(array)] => Internals.reverse(array)->Ok
| _ => Error(impossibleError)
},
(),
),
],
(),
),
]

View File

@ -0,0 +1,251 @@
open FunctionRegistry_Core
open FunctionRegistry_Helpers
let nameSpace = "Number"
let requiresNamespace = false
module NumberToNumber = {
let make = (name, fn) =>
FnDefinition.make(
~name,
~inputs=[FRTypeNumber],
~run=(_, inputs, _) => {
inputs
->getOrError(0)
->E.R.bind(Prepare.oneNumber)
->E.R2.fmap(fn)
->E.R2.fmap(Wrappers.evNumber)
},
(),
)
}
module ArrayNumberDist = {
let make = (name, fn) => {
FnDefinition.make(
~name,
~inputs=[FRTypeArray(FRTypeNumber)],
~run=(_, inputs, _) =>
Prepare.ToTypedArray.numbers(inputs)
->E.R.bind(r => E.A.length(r) === 0 ? Error("List is empty") : Ok(r))
->E.R.bind(fn),
(),
)
}
let make2 = (name, fn) => {
FnDefinition.make(
~name,
~inputs=[FRTypeArray(FRTypeAny)],
~run=(_, inputs, _) =>
Prepare.ToTypedArray.numbers(inputs)
->E.R.bind(r => E.A.length(r) === 0 ? Error("List is empty") : Ok(r))
->E.R.bind(fn),
(),
)
}
}
let library = [
Function.make(
~name="floor",
~nameSpace,
~requiresNamespace,
~output=EvtNumber,
~examples=[`floor(3.5)`],
~definitions=[NumberToNumber.make("floor", Js.Math.floor_float)],
(),
),
Function.make(
~name="ceiling",
~nameSpace,
~requiresNamespace,
~output=EvtNumber,
~examples=[`ceil(3.5)`],
~definitions=[NumberToNumber.make("ceil", Js.Math.ceil_float)],
(),
),
Function.make(
~name="absolute value",
~nameSpace,
~requiresNamespace,
~output=EvtNumber,
~examples=[`abs(3.5)`],
~definitions=[NumberToNumber.make("abs", Js.Math.abs_float)],
(),
),
Function.make(
~name="exponent",
~nameSpace,
~requiresNamespace,
~output=EvtNumber,
~examples=[`exp(3.5)`],
~definitions=[NumberToNumber.make("exp", Js.Math.exp)],
(),
),
Function.make(
~name="log",
~nameSpace,
~requiresNamespace,
~output=EvtNumber,
~examples=[`log(3.5)`],
~definitions=[NumberToNumber.make("log", Js.Math.log)],
(),
),
Function.make(
~name="log base 10",
~nameSpace,
~requiresNamespace,
~output=EvtNumber,
~examples=[`log10(3.5)`],
~definitions=[NumberToNumber.make("log10", Js.Math.log10)],
(),
),
Function.make(
~name="log base 2",
~nameSpace,
~requiresNamespace,
~output=EvtNumber,
~examples=[`log2(3.5)`],
~definitions=[NumberToNumber.make("log2", Js.Math.log2)],
(),
),
Function.make(
~name="round",
~nameSpace,
~requiresNamespace,
~output=EvtNumber,
~examples=[`round(3.5)`],
~definitions=[NumberToNumber.make("round", Js.Math.round)],
(),
),
Function.make(
~name="sum",
~nameSpace,
~requiresNamespace,
~output=EvtNumber,
~examples=[`sum([3,5,2])`],
~definitions=[ArrayNumberDist.make("sum", r => r->E.A.Floats.sum->Wrappers.evNumber->Ok)],
(),
),
Function.make(
~name="product",
~nameSpace,
~requiresNamespace,
~output=EvtNumber,
~examples=[`product([3,5,2])`],
~definitions=[
ArrayNumberDist.make("product", r => r->E.A.Floats.product->Wrappers.evNumber->Ok),
],
(),
),
Function.make(
~name="min",
~nameSpace,
~requiresNamespace,
~output=EvtNumber,
~examples=[`min([3,5,2])`],
~definitions=[ArrayNumberDist.make("min", r => r->E.A.Floats.min->Wrappers.evNumber->Ok)],
(),
),
Function.make(
~name="max",
~nameSpace,
~requiresNamespace,
~output=EvtNumber,
~examples=[`max([3,5,2])`],
~definitions=[ArrayNumberDist.make("max", r => r->E.A.Floats.max->Wrappers.evNumber->Ok)],
(),
),
Function.make(
~name="mean",
~nameSpace,
~requiresNamespace,
~output=EvtNumber,
~examples=[`mean([3,5,2])`],
~definitions=[ArrayNumberDist.make("mean", r => r->E.A.Floats.mean->Wrappers.evNumber->Ok)],
(),
),
Function.make(
~name="geometric mean",
~nameSpace,
~requiresNamespace,
~output=EvtNumber,
~examples=[`geomean([3,5,2])`],
~definitions=[
ArrayNumberDist.make("geomean", r => r->E.A.Floats.geomean->Wrappers.evNumber->Ok),
],
(),
),
Function.make(
~name="standard deviation",
~nameSpace,
~requiresNamespace,
~output=EvtNumber,
~examples=[`stdev([3,5,2,3,5])`],
~definitions=[ArrayNumberDist.make("stdev", r => r->E.A.Floats.stdev->Wrappers.evNumber->Ok)],
(),
),
Function.make(
~name="variance",
~nameSpace,
~requiresNamespace,
~output=EvtNumber,
~examples=[`variance([3,5,2,3,5])`],
~definitions=[
ArrayNumberDist.make("variance", r => r->E.A.Floats.variance->Wrappers.evNumber->Ok),
],
(),
),
Function.make(
~name="sort",
~nameSpace,
~requiresNamespace,
~output=EvtArray,
~examples=[`sort([3,5,2,3,5])`],
~definitions=[
ArrayNumberDist.make("sort", r =>
r->E.A.Floats.sort->E.A2.fmap(Wrappers.evNumber)->Wrappers.evArray->Ok
),
],
(),
),
Function.make(
~name="cumulative sum",
~nameSpace,
~requiresNamespace,
~output=EvtArray,
~examples=[`cumsum([3,5,2,3,5])`],
~definitions=[
ArrayNumberDist.make("cumsum", r =>
r->E.A.Floats.cumSum->E.A2.fmap(Wrappers.evNumber)->Wrappers.evArray->Ok
),
],
(),
),
Function.make(
~name="cumulative prod",
~nameSpace,
~requiresNamespace,
~output=EvtArray,
~examples=[`cumprod([3,5,2,3,5])`],
~definitions=[
ArrayNumberDist.make("cumprod", r =>
r->E.A.Floats.cumProd->E.A2.fmap(Wrappers.evNumber)->Wrappers.evArray->Ok
),
],
(),
),
Function.make(
~name="diff",
~nameSpace,
~requiresNamespace,
~output=EvtArray,
~examples=[`diff([3,5,2,3,5])`],
~definitions=[
ArrayNumberDist.make("diff", r =>
r->E.A.Floats.diff->E.A2.fmap(Wrappers.evNumber)->Wrappers.evArray->Ok
),
],
(),
),
]

View File

@ -0,0 +1,73 @@
open FunctionRegistry_Core
open FunctionRegistry_Helpers
let nameSpace = "Pointset"
let requiresNamespace = true
let inputsTodist = (inputs: array<FunctionRegistry_Core.frValue>, makeDist) => {
let array = inputs->getOrError(0)->E.R.bind(Prepare.ToValueArray.Array.openA)
let xyCoords =
array->E.R.bind(xyCoords =>
xyCoords
->E.A2.fmap(xyCoord =>
[xyCoord]->Prepare.ToValueArray.Record.twoArgs->E.R.bind(Prepare.ToValueTuple.twoNumbers)
)
->E.A.R.firstErrorOrOpen
)
let expressionValue =
xyCoords
->E.R.bind(r => r->XYShape.T.makeFromZipped->E.R2.errMap(XYShape.Error.toString))
->E.R2.fmap(r => ReducerInterface_InternalExpressionValue.IEvDistribution(
PointSet(makeDist(r)),
))
expressionValue
}
let library = [
Function.make(
~name="makeContinuous",
~nameSpace,
~requiresNamespace,
~examples=[
`Pointset.makeContinuous([
{x: 0, y: 0.2},
{x: 1, y: 0.7},
{x: 2, y: 0.8},
{x: 3, y: 0.2}
])`,
],
~output=ReducerInterface_InternalExpressionValue.EvtDistribution,
~definitions=[
FnDefinition.make(
~name="makeContinuous",
~inputs=[FRTypeArray(FRTypeRecord([("x", FRTypeNumeric), ("y", FRTypeNumeric)]))],
~run=(_, inputs, _) => inputsTodist(inputs, r => Continuous(Continuous.make(r))),
(),
),
],
(),
),
Function.make(
~name="makeDiscrete",
~nameSpace,
~requiresNamespace,
~examples=[
`Pointset.makeDiscrete([
{x: 0, y: 0.2},
{x: 1, y: 0.7},
{x: 2, y: 0.8},
{x: 3, y: 0.2}
])`,
],
~output=ReducerInterface_InternalExpressionValue.EvtDistribution,
~definitions=[
FnDefinition.make(
~name="makeDiscrete",
~inputs=[FRTypeArray(FRTypeRecord([("x", FRTypeNumeric), ("y", FRTypeNumeric)]))],
~run=(_, inputs, _) => inputsTodist(inputs, r => Discrete(Discrete.make(r))),
(),
),
],
(),
),
]

View File

@ -0,0 +1,89 @@
open FunctionRegistry_Core
let nameSpace = "Dist"
let requiresNamespace = true
let runScoring = (estimate, answer, prior, env) => {
GenericDist.Score.logScore(~estimate, ~answer, ~prior, ~env)
->E.R2.fmap(FunctionRegistry_Helpers.Wrappers.evNumber)
->E.R2.errMap(DistributionTypes.Error.toString)
}
let library = [
Function.make(
~name="logScore",
~nameSpace,
~requiresNamespace,
~output=EvtNumber,
~examples=[
"Dist.logScore({estimate: normal(5,2), answer: normal(5.2,1), prior: normal(5.5,3)})",
"Dist.logScore({estimate: normal(5,2), answer: normal(5.2,1)})",
"Dist.logScore({estimate: normal(5,2), answer: 4.5})",
],
~definitions=[
FnDefinition.make(
~name="logScore",
~inputs=[
FRTypeRecord([
("estimate", FRTypeDist),
("answer", FRTypeDistOrNumber),
("prior", FRTypeDist),
]),
],
~run=(_, inputs, env) => {
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.threeArgs(inputs) {
| Ok([FRValueDist(estimate), FRValueDistOrNumber(FRValueDist(d)), FRValueDist(prior)]) =>
runScoring(estimate, Score_Dist(d), Some(prior), env)
| Ok([
FRValueDist(estimate),
FRValueDistOrNumber(FRValueNumber(d)),
FRValueDist(prior),
]) =>
runScoring(estimate, Score_Scalar(d), Some(prior), env)
| Error(e) => Error(e)
| _ => Error(FunctionRegistry_Helpers.impossibleError)
}
},
(),
),
FnDefinition.make(
~name="logScore",
~inputs=[FRTypeRecord([("estimate", FRTypeDist), ("answer", FRTypeDistOrNumber)])],
~run=(_, inputs, env) => {
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.twoArgs(inputs) {
| Ok([FRValueDist(estimate), FRValueDistOrNumber(FRValueDist(d))]) =>
runScoring(estimate, Score_Dist(d), None, env)
| Ok([FRValueDist(estimate), FRValueDistOrNumber(FRValueNumber(d))]) =>
runScoring(estimate, Score_Scalar(d), None, env)
| Error(e) => Error(e)
| _ => Error(FunctionRegistry_Helpers.impossibleError)
}
},
(),
),
],
(),
),
Function.make(
~name="klDivergence",
~nameSpace,
~output=EvtNumber,
~requiresNamespace,
~examples=["Dist.klDivergence(normal(5,2), normal(5,1.5))"],
~definitions=[
FnDefinition.make(
~name="klDivergence",
~inputs=[FRTypeDist, FRTypeDist],
~run=(_, inputs, env) => {
switch inputs {
| [FRValueDist(estimate), FRValueDist(d)] =>
runScoring(estimate, Score_Dist(d), None, env)
| _ => Error(FunctionRegistry_Helpers.impossibleError)
}
},
(),
),
],
(),
),
]

View File

@ -1,3 +1,6 @@
// Only Bindings as the global module is supported
// Other module operations such as import export will be prepreocessed jobs
module ExpressionT = Reducer_Expression_T module ExpressionT = Reducer_Expression_T
module InternalExpressionValue = ReducerInterface_InternalExpressionValue module InternalExpressionValue = ReducerInterface_InternalExpressionValue
open Reducer_ErrorValue open Reducer_ErrorValue

View File

@ -10,8 +10,8 @@ open ReducerInterface_InternalExpressionValue
open Reducer_ErrorValue open Reducer_ErrorValue
/* /*
MathJs provides default implementations for builtins MathJs provides default implementations for built-ins
This is where all the expected builtins like + = * / sin cos log ln etc are handled This is where all the expected built-ins like + = * / sin cos log ln etc are handled
DO NOT try to add external function mapping here! DO NOT try to add external function mapping here!
*/ */
@ -149,6 +149,27 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
doLambdaCall(aLambdaValue, list{IEvNumber(a), IEvNumber(b), IEvNumber(c)}) doLambdaCall(aLambdaValue, list{IEvNumber(a), IEvNumber(b), IEvNumber(c)})
SampleSetDist.map3(~fn, ~t1, ~t2, ~t3)->toType SampleSetDist.map3(~fn, ~t1, ~t2, ~t3)->toType
} }
let parseSampleSetArray = (arr: array<internalExpressionValue>): option<
array<SampleSetDist.t>,
> => {
let parseSampleSet = (value: internalExpressionValue): option<SampleSetDist.t> =>
switch value {
| IEvDistribution(SampleSet(dist)) => Some(dist)
| _ => None
}
E.A.O.openIfAllSome(E.A.fmap(parseSampleSet, arr))
}
let mapN = (aValueArray: array<internalExpressionValue>, aLambdaValue) => {
switch parseSampleSetArray(aValueArray) {
| Some(t1) =>
let fn = a => doLambdaCall(aLambdaValue, list{IEvArray(E.A.fmap(x => IEvNumber(x), a))})
SampleSetDist.mapN(~fn, ~t1)->toType
| None =>
Error(REFunctionNotFound(call->functionCallToCallSignature->functionCallSignatureToString))
}
}
} }
let doReduceArray = (aValueArray, initialValue, aLambdaValue) => { let doReduceArray = (aValueArray, initialValue, aLambdaValue) => {
@ -198,7 +219,7 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
| ("$_typeFunction_$", [IEvArray(arr)]) => TypeBuilder.typeFunction(arr) | ("$_typeFunction_$", [IEvArray(arr)]) => TypeBuilder.typeFunction(arr)
| ("$_typeTuple_$", [IEvArray(elems)]) => TypeBuilder.typeTuple(elems) | ("$_typeTuple_$", [IEvArray(elems)]) => TypeBuilder.typeTuple(elems)
| ("$_typeArray_$", [elem]) => TypeBuilder.typeArray(elem) | ("$_typeArray_$", [elem]) => TypeBuilder.typeArray(elem)
| ("$_typeRecord_$", [IEvArray(arrayOfPairs)]) => TypeBuilder.typeRecord(arrayOfPairs) | ("$_typeRecord_$", [IEvRecord(propertyMap)]) => TypeBuilder.typeRecord(propertyMap)
| ("concat", [IEvArray(aValueArray), IEvArray(bValueArray)]) => | ("concat", [IEvArray(aValueArray), IEvArray(bValueArray)]) =>
doAddArray(aValueArray, bValueArray) doAddArray(aValueArray, bValueArray)
| ("concat", [IEvString(aValueString), IEvString(bValueString)]) => | ("concat", [IEvString(aValueString), IEvString(bValueString)]) =>
@ -230,6 +251,8 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
], ],
) => ) =>
SampleMap.map3(dist1, dist2, dist3, aLambdaValue) SampleMap.map3(dist1, dist2, dist3, aLambdaValue)
| ("mapSamplesN", [IEvArray(aValueArray), IEvLambda(aLambdaValue)]) =>
SampleMap.mapN(aValueArray, aLambdaValue)
| ("reduce", [IEvArray(aValueArray), initialValue, IEvLambda(aLambdaValue)]) => | ("reduce", [IEvArray(aValueArray), initialValue, IEvLambda(aLambdaValue)]) =>
doReduceArray(aValueArray, initialValue, aLambdaValue) doReduceArray(aValueArray, initialValue, aLambdaValue)
| ("reduceReverse", [IEvArray(aValueArray), initialValue, IEvLambda(aLambdaValue)]) => | ("reduceReverse", [IEvArray(aValueArray), initialValue, IEvLambda(aLambdaValue)]) =>
@ -246,7 +269,6 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
Error(REFunctionNotFound(call->functionCallToCallSignature->functionCallSignatureToString)) // Report full type signature as error Error(REFunctionNotFound(call->functionCallToCallSignature->functionCallSignatureToString)) // Report full type signature as error
} }
} }
/* /*
Reducer uses Result monad while reducing expressions Reducer uses Result monad while reducing expressions
*/ */
@ -255,11 +277,10 @@ let dispatch = (call: functionCall, environment, reducer: ExpressionT.reducerFn)
errorValue, errorValue,
> => > =>
try { try {
let callInternalWithReducer = (call, environment) => callInternal(call, environment, reducer)
let (fn, args) = call let (fn, args) = call
// There is a bug that prevents string match in patterns // There is a bug that prevents string match in patterns
// So we have to recreate a copy of the string // So we have to recreate a copy of the string
ExternalLibrary.dispatch((Js.String.make(fn), args), environment, callInternalWithReducer) ExternalLibrary.dispatch((Js.String.make(fn), args), environment, reducer, callInternal)
} catch { } catch {
| Js.Exn.Error(obj) => REJavaScriptExn(Js.Exn.message(obj), Js.Exn.name(obj))->Error | Js.Exn.Error(obj) => REJavaScriptExn(Js.Exn.message(obj), Js.Exn.name(obj))->Error
| _ => RETodo("unhandled rescript exception")->Error | _ => RETodo("unhandled rescript exception")->Error

View File

@ -144,7 +144,7 @@ let dispatchMacroCall = (
let ifTrueBlock = eBlock(list{ifTrue}) let ifTrueBlock = eBlock(list{ifTrue})
ExpressionWithContext.withContext(ifTrueBlock, bindings)->Ok ExpressionWithContext.withContext(ifTrueBlock, bindings)->Ok
} }
| _ => REExpectedType("Boolean")->Error | _ => REExpectedType("Boolean", "")->Error
} }
) )
} }

View File

@ -7,7 +7,7 @@ type errorValue =
| REArrayIndexNotFound(string, int) | REArrayIndexNotFound(string, int)
| REAssignmentExpected | REAssignmentExpected
| REDistributionError(DistributionTypes.error) | REDistributionError(DistributionTypes.error)
| REExpectedType(string) | REExpectedType(string, string)
| REExpressionExpected | REExpressionExpected
| REFunctionExpected(string) | REFunctionExpected(string)
| REFunctionNotFound(string) | REFunctionNotFound(string)
@ -55,6 +55,6 @@ let errorToString = err =>
| RESymbolNotFound(symbolName) => `${symbolName} is not defined` | RESymbolNotFound(symbolName) => `${symbolName} is not defined`
| RESyntaxError(desc, _) => `Syntax Error: ${desc}` | RESyntaxError(desc, _) => `Syntax Error: ${desc}`
| RETodo(msg) => `TODO: ${msg}` | RETodo(msg) => `TODO: ${msg}`
| REExpectedType(typeName) => `Expected type: ${typeName}` | REExpectedType(typeName, valueString) => `Expected type: ${typeName} but got: ${valueString}`
| REUnitNotFound(unitName) => `Unit not found: ${unitName}` | REUnitNotFound(unitName) => `Unit not found: ${unitName}`
} }

View File

@ -1,3 +1,3 @@
// There are switch stament cases in the code which are impossible to reach by design. // There are switch statement cases in the code which are impossible to reach by design.
// ImpossibleException is a sign of programming error. // ImpossibleException is a sign of programming error.
exception ImpossibleException exception ImpossibleException(string)

View File

@ -30,12 +30,12 @@ let rec toString = expression =>
switch expression { switch expression {
| EList(list{EValue(IEvCall("$$_block_$$")), ...statements}) => | EList(list{EValue(IEvCall("$$_block_$$")), ...statements}) =>
`{${Belt.List.map(statements, aValue => toString(aValue)) `{${Belt.List.map(statements, aValue => toString(aValue))
->Extra.List.interperse("; ") ->Extra.List.intersperse("; ")
->Belt.List.toArray ->Belt.List.toArray
->Js.String.concatMany("")}}` ->Js.String.concatMany("")}}`
| EList(aList) => | EList(aList) =>
`(${Belt.List.map(aList, aValue => toString(aValue)) `(${Belt.List.map(aList, aValue => toString(aValue))
->Extra.List.interperse(" ") ->Extra.List.intersperse(" ")
->Belt.List.toArray ->Belt.List.toArray
->Js.String.concatMany("")})` ->Js.String.concatMany("")})`
| EValue(aValue) => InternalExpressionValue.toString(aValue) | EValue(aValue) => InternalExpressionValue.toString(aValue)

View File

@ -3,5 +3,5 @@
*/ */
module ExtraList = Reducer_Extra_List module ExtraList = Reducer_Extra_List
let interperse = (anArray, seperator) => let intersperse = (anArray, seperator) =>
anArray->Belt.List.fromArray->ExtraList.interperse(seperator)->Belt.List.toArray anArray->Belt.List.fromArray->ExtraList.intersperse(seperator)->Belt.List.toArray

View File

@ -1,9 +1,9 @@
/* /*
Insert seperator between the elements of a list Insert seperator between the elements of a list
*/ */
let rec interperse = (aList, seperator) => let rec intersperse = (aList, seperator) =>
switch aList { switch aList {
| list{} => list{} | list{} => list{}
| list{a} => list{a} | list{a} => list{a}
| list{a, ...rest} => list{a, seperator, ...interperse(rest, seperator)} | list{a, ...rest} => list{a, seperator, ...intersperse(rest, seperator)}
} }

View File

@ -91,7 +91,7 @@ let rec pgToString = (peggyNode: peggyNode): string => {
args->Js.Array2.map(arg => PgNodeIdentifier(arg)->pgToString)->Js.Array2.toString args->Js.Array2.map(arg => PgNodeIdentifier(arg)->pgToString)->Js.Array2.toString
let nodesToStringUsingSeparator = (nodes: array<node>, separator: string): string => let nodesToStringUsingSeparator = (nodes: array<node>, separator: string): string =>
nodes->Js.Array2.map(toString)->Extra.Array.interperse(separator)->Js.String.concatMany("") nodes->Js.Array2.map(toString)->Extra.Array.intersperse(separator)->Js.String.concatMany("")
switch peggyNode { switch peggyNode {
| PgNodeBlock(node) => "{" ++ node["statements"]->nodesToStringUsingSeparator("; ") ++ "}" | PgNodeBlock(node) => "{" ++ node["statements"]->nodesToStringUsingSeparator("; ") ++ "}"

View File

@ -0,0 +1,40 @@
module ErrorValue = Reducer_ErrorValue
module ExpressionT = Reducer_Expression_T
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Bindings = Reducer_Bindings
module T = Reducer_Type_T
let ievFromTypeExpression = (
typeExpressionSourceCode: string,
reducerFn: ExpressionT.reducerFn,
): result<InternalExpressionValue.t, ErrorValue.t> => {
let sIndex = "compiled"
let sourceCode = `type ${sIndex}=${typeExpressionSourceCode}`
Reducer_Expression.parse(sourceCode)->Belt.Result.flatMap(expr => {
let rContext = reducerFn(
expr,
Bindings.emptyBindings,
InternalExpressionValue.defaultEnvironment,
)
Belt.Result.map(rContext, context =>
switch context {
| IEvBindings(nameSpace) =>
switch Bindings.getType(nameSpace, sIndex) {
| Some(value) => value
| None => raise(Reducer_Exception.ImpossibleException("Reducer_Type_Compile-none"))
}
| _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_Compile-raise"))
}
)
})
}
let fromTypeExpression = (
typeExpressionSourceCode: string,
reducerFn: ExpressionT.reducerFn,
): result<T.t, ErrorValue.t> => {
ievFromTypeExpression(
(typeExpressionSourceCode: string),
(reducerFn: ExpressionT.reducerFn),
)->Belt.Result.map(T.fromIEvValue)
}

View File

@ -0,0 +1,53 @@
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module T = Reducer_Type_T
let isMin = (modifierArg: InternalExpressionValue.t, aValue: InternalExpressionValue.t): bool => {
let pair = (modifierArg, aValue)
switch pair {
| (IEvNumber(a), IEvNumber(b)) => a <= b
| _ => false
}
}
let isMax = (modifierArg: InternalExpressionValue.t, aValue: InternalExpressionValue.t): bool => {
let pair = (modifierArg, aValue)
switch pair {
| (IEvNumber(a), IEvNumber(b)) => a >= b
| _ => false
}
}
let isMemberOf = (
modifierArg: InternalExpressionValue.t,
aValue: InternalExpressionValue.t,
): bool => {
let pair = (modifierArg, aValue)
switch pair {
| (ievA, IEvArray(b)) => Js.Array2.includes(b, ievA)
| _ => false
}
}
let checkModifier = (
key: string,
modifierArg: InternalExpressionValue.t,
aValue: InternalExpressionValue.t,
): bool =>
switch key {
| "min" => isMin(modifierArg, aValue)
| "max" => isMax(modifierArg, aValue)
| "isMemberOf" => isMemberOf(modifierArg, aValue)
| _ => false
}
let checkModifiers = (
contracts: Belt.Map.String.t<InternalExpressionValue.t>,
aValue: InternalExpressionValue.t,
): bool => {
contracts->Belt.Map.String.reduce(true, (acc, key, modifierArg) =>
switch acc {
| true => checkModifier(key, modifierArg, aValue)
| _ => acc
}
)
}

View File

@ -3,13 +3,42 @@ open InternalExpressionValue
type rec iType = type rec iType =
| ItTypeIdentifier(string) | ItTypeIdentifier(string)
| ItModifiedType({modifiedType: iType}) | ItModifiedType({modifiedType: iType, contracts: Belt.Map.String.t<InternalExpressionValue.t>})
| ItTypeOr({typeOr: array<iType>}) | ItTypeOr({typeOr: array<iType>})
| ItTypeFunction({inputs: array<iType>, output: iType}) | ItTypeFunction({inputs: array<iType>, output: iType})
| ItTypeArray({element: iType}) | ItTypeArray({element: iType})
| ItTypeTuple({elements: array<iType>}) | ItTypeTuple({elements: array<iType>})
| ItTypeRecord({properties: Belt.Map.String.t<iType>}) | ItTypeRecord({properties: Belt.Map.String.t<iType>})
type t = iType
type typeErrorValue = TypeMismatch(t, InternalExpressionValue.t)
let rec toString = (t: t): string => {
switch t {
| ItTypeIdentifier(s) => s
| ItModifiedType({modifiedType, contracts}) =>
`${toString(modifiedType)}${contracts->Belt.Map.String.reduce("", (acc, k, v) =>
Js.String2.concatMany(acc, ["<-", k, "(", InternalExpressionValue.toString(v), ")"])
)}`
| ItTypeOr({typeOr}) => `(${Js.Array2.map(typeOr, toString)->Js.Array2.joinWith(" | ")})`
| ItTypeFunction({inputs, output}) =>
`(${inputs->Js.Array2.map(toString)->Js.Array2.joinWith(" => ")} => ${toString(output)})`
| ItTypeArray({element}) => `[${toString(element)}]`
| ItTypeTuple({elements}) => `[${Js.Array2.map(elements, toString)->Js.Array2.joinWith(", ")}]`
| ItTypeRecord({properties}) =>
`{${properties
->Belt.Map.String.toArray
->Js.Array2.map(((k, v)) => Js.String2.concatMany(k, [": ", toString(v)]))
->Js.Array2.joinWith(", ")}}`
}
}
let toStringResult = (rt: result<t, ErrorValue.t>) =>
switch rt {
| Ok(t) => toString(t)
| Error(e) => ErrorValue.errorToString(e)
}
let rec fromTypeMap = typeMap => { let rec fromTypeMap = typeMap => {
let default = IEvString("") let default = IEvString("")
let evTypeTag: InternalExpressionValue.t = Belt.Map.String.getWithDefault( let evTypeTag: InternalExpressionValue.t = Belt.Map.String.getWithDefault(
@ -52,31 +81,39 @@ let rec fromTypeMap = typeMap => {
"properties", "properties",
default, default,
) )
//TODO: map type modifiers
switch evTypeTag { let contracts =
| IEvString("typeIdentifier") => ItModifiedType({modifiedType: fromIEvValue(evTypeIdentifier)}) typeMap->Belt.Map.String.keep((k, _v) => ["min", "max", "memberOf"]->Js.Array2.includes(k))
let makeIt = switch evTypeTag {
| IEvString("typeIdentifier") => fromIEvValue(evTypeIdentifier)
| IEvString("typeOr") => ItTypeOr({typeOr: fromIEvArray(evTypeOr)}) | IEvString("typeOr") => ItTypeOr({typeOr: fromIEvArray(evTypeOr)})
| IEvString("typeFunction") => | IEvString("typeFunction") =>
ItTypeFunction({inputs: fromIEvArray(evInputs), output: fromIEvValue(evOutput)}) ItTypeFunction({inputs: fromIEvArray(evInputs), output: fromIEvValue(evOutput)})
| IEvString("typeArray") => ItTypeArray({element: fromIEvValue(evElement)}) | IEvString("typeArray") => ItTypeArray({element: fromIEvValue(evElement)})
| IEvString("typeTuple") => ItTypeTuple({elements: fromIEvArray(evElements)}) | IEvString("typeTuple") => ItTypeTuple({elements: fromIEvArray(evElements)})
| IEvString("typeRecord") => ItTypeRecord({properties: fromIEvRecord(evProperties)}) | IEvString("typeRecord") => ItTypeRecord({properties: fromIEvRecord(evProperties)})
| _ => raise(Reducer_Exception.ImpossibleException) | _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_T-evTypeTag"))
} }
Belt.Map.String.isEmpty(contracts)
? makeIt
: ItModifiedType({modifiedType: makeIt, contracts: contracts})
} }
and fromIEvValue = (ievValue: InternalExpressionValue.t) =>
and fromIEvValue = (ievValue: InternalExpressionValue.t): iType =>
switch ievValue { switch ievValue {
| IEvTypeIdentifier(typeIdentifier) => ItTypeIdentifier({typeIdentifier}) | IEvTypeIdentifier(typeIdentifier) => ItTypeIdentifier({typeIdentifier})
| IEvType(typeMap) => fromTypeMap(typeMap) | IEvType(typeMap) => fromTypeMap(typeMap)
| _ => raise(Reducer_Exception.ImpossibleException) | _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_T-ievValue"))
} }
and fromIEvArray = (ievArray: InternalExpressionValue.t) => and fromIEvArray = (ievArray: InternalExpressionValue.t) =>
switch ievArray { switch ievArray {
| IEvArray(array) => array->Belt.Array.map(fromIEvValue) | IEvArray(array) => array->Belt.Array.map(fromIEvValue)
| _ => raise(Reducer_Exception.ImpossibleException) | _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_T-ievArray"))
} }
and fromIEvRecord = (ievRecord: InternalExpressionValue.t) => and fromIEvRecord = (ievRecord: InternalExpressionValue.t) =>
switch ievRecord { switch ievRecord {
| IEvRecord(record) => record->Belt.Map.String.map(fromIEvValue) | IEvRecord(record) => record->Belt.Map.String.map(fromIEvValue)
| _ => raise(Reducer_Exception.ImpossibleException) | _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_T-ievRecord"))
} }

View File

@ -56,7 +56,7 @@ let typeFunction = anArray => {
let typeArray = element => { let typeArray = element => {
let newRecord = Belt.Map.String.fromArray([ let newRecord = Belt.Map.String.fromArray([
("typeTag", IEvString("typeTuple")), ("typeTag", IEvString("typeArray")),
("element", element), ("element", element),
]) ])
newRecord->IEvType->Ok newRecord->IEvType->Ok
@ -64,22 +64,14 @@ let typeArray = element => {
let typeTuple = anArray => { let typeTuple = anArray => {
let newRecord = Belt.Map.String.fromArray([ let newRecord = Belt.Map.String.fromArray([
("typeTag", IEvString("typeArray")), ("typeTag", IEvString("typeTuple")),
("elements", IEvArray(anArray)), ("elements", IEvArray(anArray)),
]) ])
newRecord->IEvType->Ok newRecord->IEvType->Ok
} }
let typeRecord = arrayOfPairs => { let typeRecord = propertyMap => {
let newProperties = let newProperties = propertyMap->IEvRecord
Belt.Array.map(arrayOfPairs, pairValue =>
switch pairValue {
| IEvArray([IEvString(key), valueValue]) => (key, valueValue)
| _ => ("wrong key type", pairValue->toStringWithType->IEvString)
}
)
->Belt.Map.String.fromArray
->IEvRecord
let newRecord = Belt.Map.String.fromArray([ let newRecord = Belt.Map.String.fromArray([
("typeTag", IEvString("typeRecord")), ("typeTag", IEvString("typeRecord")),
("properties", newProperties), ("properties", newProperties),

View File

@ -1,81 +1,168 @@
module ExpressionT = Reducer_Expression_T module ExpressionT = Reducer_Expression_T
module InternalExpressionValue = ReducerInterface_InternalExpressionValue module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module T = Reducer_Type_T module T = Reducer_Type_T
module TypeBuilder = Reducer_Type_TypeBuilder module TypeContracts = Reducer_Type_Contracts
open InternalExpressionValue open InternalExpressionValue
type typeErrorValue = let rec isITypeOf = (anIType: T.iType, aValue): result<bool, T.typeErrorValue> => {
| TypeError(T.iType, InternalExpressionValue.t)
| TypeErrorWithPosition(T.iType, InternalExpressionValue.t, int)
| TypeErrorWithProperty(T.iType, InternalExpressionValue.t, string)
let rec isOfResolvedIType = (anIType: T.iType, aValue): result<bool, typeErrorValue> => {
let caseTypeIdentifier = (anUpperTypeName, aValue) => { let caseTypeIdentifier = (anUpperTypeName, aValue) => {
let aTypeName = anUpperTypeName->Js.String2.toLowerCase let aTypeName = anUpperTypeName->Js.String2.toLowerCase
let valueTypeName = aValue->valueToValueType->valueTypeToString->Js.String2.toLowerCase let valueTypeName = aValue->valueToValueType->valueTypeToString->Js.String2.toLowerCase
switch aTypeName === valueTypeName { switch aTypeName == valueTypeName {
| true => Ok(true) | true => Ok(true)
| false => TypeError(anIType, aValue)->Error | false => T.TypeMismatch(anIType, aValue)->Error
} }
} }
let _caseRecord = (anIType, evValue, propertyMap, map) => { let caseRecord = (anIType, propertyMap: Belt.Map.String.t<T.iType>, evValue) =>
Belt.Map.String.reduce(propertyMap, Ok(true), (acc, property, propertyType) => { switch evValue {
| IEvRecord(aRecord) =>
if (
Js.Array2.length(propertyMap->Belt.Map.String.keysToArray) ==
Js.Array2.length(aRecord->Belt.Map.String.keysToArray)
) {
Belt.Map.String.reduce(propertyMap, Ok(true), (acc, property, propertyType) => {
Belt.Result.flatMap(acc, _ =>
switch Belt.Map.String.get(aRecord, property) {
| Some(propertyValue) => isITypeOf(propertyType, propertyValue)
| None => T.TypeMismatch(anIType, evValue)->Error
}
)
})
} else {
T.TypeMismatch(anIType, evValue)->Error
}
| _ => T.TypeMismatch(anIType, evValue)->Error
}
let caseArray = (anIType, elementType, evValue) =>
switch evValue {
| IEvArray(anArray) =>
Belt.Array.reduce(anArray, Ok(true), (acc, element) =>
Belt.Result.flatMap(acc, _ =>
switch isITypeOf(elementType, element) {
| Ok(_) => Ok(true)
| Error(error) => error->Error
}
)
)
| _ => T.TypeMismatch(anIType, evValue)->Error
}
let caseTuple = (anIType, elementTypes, evValue) =>
switch evValue {
| IEvArray(anArray) =>
if Js.Array2.length(elementTypes) == Js.Array2.length(anArray) {
let zipped = Belt.Array.zip(elementTypes, anArray)
Belt.Array.reduce(zipped, Ok(true), (acc, (elementType, element)) =>
switch acc {
| Ok(_) =>
switch isITypeOf(elementType, element) {
| Ok(_) => acc
| Error(error) => Error(error)
}
| _ => acc
}
)
} else {
T.TypeMismatch(anIType, evValue)->Error
}
| _ => T.TypeMismatch(anIType, evValue)->Error
}
let caseOr = (anIType, anITypeArray, evValue) =>
switch Belt.Array.reduce(anITypeArray, Ok(false), (acc, anIType) =>
Belt.Result.flatMap(acc, _ => Belt.Result.flatMap(acc, _ =>
switch Belt.Map.String.get(map, property) { switch acc {
| Some(propertyValue) => isOfResolvedIType(propertyType, propertyValue) | Ok(false) =>
| None => TypeErrorWithProperty(anIType, evValue, property)->Error switch isITypeOf(anIType, evValue) {
| Ok(_) => Ok(true)
| Error(_) => acc
}
| _ => acc
} }
) )
}) ) {
} | Ok(true) => Ok(true)
let _caseArray = (anIType, evValue, elementType, anArray) => { | Ok(false) => T.TypeMismatch(anIType, evValue)->Error
Belt.Array.reduceWithIndex(anArray, Ok(true), (acc, element, index) => { | Error(error) => Error(error)
switch isOfResolvedIType(elementType, element) { }
| Ok(_) => acc
| Error(_) => TypeErrorWithPosition(anIType, evValue, index)->Error let caseModifiedType = (
anIType: T.iType,
modifiedType: T.iType,
contracts: Belt.Map.String.t<InternalExpressionValue.t>,
aValue: InternalExpressionValue.t,
) => {
isITypeOf(modifiedType, aValue)->Belt.Result.flatMap(_result => {
if TypeContracts.checkModifiers(contracts, aValue) {
Ok(true)
} else {
T.TypeMismatch(anIType, aValue)->Error
} }
}) })
} }
switch anIType { switch anIType {
| ItTypeIdentifier(name) => caseTypeIdentifier(name, aValue) | ItTypeIdentifier(name) => caseTypeIdentifier(name, aValue)
// TODO: Work in progress. Code is commented to make an a release of other features | ItModifiedType({modifiedType, contracts}) =>
// | ItModifiedType({modifiedType: anIType}) => raise(Reducer_Exception.ImpossibleException) caseModifiedType(anIType, modifiedType, contracts, aValue) //{modifiedType: iType, contracts: Belt.Map.String.t<InternalExpressionValue.t>}
// | ItTypeOr({typeOr: anITypeArray}) => raise(Reducer_Exception.ImpossibleException) | ItTypeOr({typeOr}) => caseOr(anIType, typeOr, aValue)
// | ItTypeFunction({inputs: anITypeArray, output: anIType}) => | ItTypeFunction(_) =>
// raise(Reducer_Exception.ImpossibleException) raise(
// | ItTypeArray({element: anIType}) => raise(Reducer_Exception.ImpossibleException) Reducer_Exception.ImpossibleException(
// | ItTypeTuple({elements: anITypeArray}) => raise(Reducer_Exception.ImpossibleException) "Reducer_TypeChecker-functions are without a type at the moment",
// | ItTypeRecord({properties: anITypeMap}) => raise(Reducer_Exception.ImpossibleException) ),
| _ => raise(Reducer_Exception.ImpossibleException) )
| ItTypeArray({element}) => caseArray(anIType, element, aValue)
| ItTypeTuple({elements}) => caseTuple(anIType, elements, aValue)
| ItTypeRecord({properties}) => caseRecord(anIType, properties, aValue)
} }
} }
let isOfResolvedType = (aType: InternalExpressionValue.t, aValue): result<bool, typeErrorValue> => let isTypeOf = (
aType->T.fromIEvValue->isOfResolvedIType(aValue) typeExpressionSourceCode: string,
aValue: InternalExpressionValue.t,
reducerFn: ExpressionT.reducerFn,
): result<InternalExpressionValue.t, ErrorValue.t> => {
switch typeExpressionSourceCode->Reducer_Type_Compile.fromTypeExpression(reducerFn) {
| Ok(anIType) =>
switch isITypeOf(anIType, aValue) {
| Ok(_) => Ok(aValue)
| Error(T.TypeMismatch(anIType, evValue)) =>
Error(
ErrorValue.REExpectedType(anIType->T.toString, evValue->InternalExpressionValue.toString),
)
}
| Error(error) => Error(error) // Directly propagating - err => err - causes type mismatch
}
}
// TODO: Work in progress. Code is commented to make an a release of other features let checkITypeArguments = (anIType: T.iType, args: array<InternalExpressionValue.t>): result<
// let checkArguments = ( bool,
// evFunctionType: InternalExpressionValue.t, T.typeErrorValue,
// args: array<InternalExpressionValue.t>, > => {
// ) => { switch anIType {
// let functionType = switch evFunctionType { | T.ItTypeFunction({inputs}) => isITypeOf(T.ItTypeTuple({elements: inputs}), args->IEvArray)
// | IEvRecord(functionType) => functionType | _ => T.TypeMismatch(anIType, args->IEvArray)->Error
// | _ => raise(Reducer_Exception.ImpossibleException) }
// } }
// let evInputs = functionType->Belt.Map.String.getWithDefault("inputs", []->IEvArray)
// let inputs = switch evInputs {
// | IEvArray(inputs) => inputs
// | _ => raise(Reducer_Exception.ImpossibleException)
// }
// let rTupleType = TypeBuilder.typeTuple(inputs)
// Belt.Result.flatMap(rTupleType, tuppleType => isOfResolvedType(tuppleType, args->IEvArray))
// }
// let compileTypeExpression = (typeExpression: string, bindings: ExpressionT.bindings, reducerFn: ExpressionT.reducerFn) => { let checkArguments = (
// statement = `type compiled=${typeExpression}` typeExpressionSourceCode: string,
args: array<InternalExpressionValue.t>,
// } reducerFn: ExpressionT.reducerFn,
): result<InternalExpressionValue.t, ErrorValue.t> => {
//TODO: asGuard switch typeExpressionSourceCode->Reducer_Type_Compile.fromTypeExpression(reducerFn) {
| Ok(anIType) =>
switch checkITypeArguments(anIType, args) {
| Ok(_) => Ok(args->IEvArray)
| Error(T.TypeMismatch(anIType, evValue)) =>
Error(
ErrorValue.REExpectedType(anIType->T.toString, evValue->InternalExpressionValue.toString),
)
}
| Error(error) => Error(error) // Directly propagating - err => err - causes type mismatch
}
}

View File

@ -1,29 +1,10 @@
module InternalExpressionValue = ReducerInterface_InternalExpressionValue module InternalExpressionValue = ReducerInterface_InternalExpressionValue
type internalExpressionValue = InternalExpressionValue.t type internalExpressionValue = InternalExpressionValue.t
// module Sample = {
// // In real life real libraries should be somewhere else
// /*
// For an example of mapping polymorphic custom functions. To be deleted after real integration
// */
// let customAdd = (a: float, b: float): float => {a +. b}
// }
/* /*
Map external calls of Reducer Map external calls of Reducer
*/ */
let dispatch = (call: InternalExpressionValue.functionCall, environment, reducer, chain): result<
// I expect that it's important to build this first, so it doesn't get recalculated for each tryRegistry() call.
let registry = FunctionRegistry_Library.registry
let tryRegistry = ((fnName, args): InternalExpressionValue.functionCall, env) => {
FunctionRegistry_Core.Registry.matchAndRun(~registry, ~fnName, ~args, ~env)->E.O2.fmap(
E.R2.errMap(_, s => Reducer_ErrorValue.RETodo(s)),
)
}
let dispatch = (call: InternalExpressionValue.functionCall, environment, chain): result<
internalExpressionValue, internalExpressionValue,
'e, 'e,
> => { > => {
@ -32,9 +13,10 @@ let dispatch = (call: InternalExpressionValue.functionCall, environment, chain):
() => ReducerInterface_Date.dispatch(call, environment), () => ReducerInterface_Date.dispatch(call, environment),
() => ReducerInterface_Duration.dispatch(call, environment), () => ReducerInterface_Duration.dispatch(call, environment),
() => ReducerInterface_Number.dispatch(call, environment), () => ReducerInterface_Number.dispatch(call, environment),
() => tryRegistry(call, environment), () => FunctionRegistry_Library.dispatch(call, environment),
])->E.O2.default(chain(call, environment)) ])->E.O2.default(chain(call, environment, reducer))
} }
/* /*
If your dispatch is too big you can divide it into smaller dispatches and pass the call so that it gets called finally. If your dispatch is too big you can divide it into smaller dispatches and pass the call so that it gets called finally.

View File

@ -84,7 +84,7 @@ let toStringWithType = aValue =>
| IEvDeclaration(_) => `Declaration::${toString(aValue)}` | IEvDeclaration(_) => `Declaration::${toString(aValue)}`
| IEvDistribution(_) => `Distribution::${toString(aValue)}` | IEvDistribution(_) => `Distribution::${toString(aValue)}`
| IEvLambda(_) => `Lambda::${toString(aValue)}` | IEvLambda(_) => `Lambda::${toString(aValue)}`
| IEvBindings(_) => `Module::${toString(aValue)}` | IEvBindings(_) => `Bindings::${toString(aValue)}`
| IEvNumber(_) => `Number::${toString(aValue)}` | IEvNumber(_) => `Number::${toString(aValue)}`
| IEvRecord(_) => `Record::${toString(aValue)}` | IEvRecord(_) => `Record::${toString(aValue)}`
| IEvString(_) => `String::${toString(aValue)}` | IEvString(_) => `String::${toString(aValue)}`
@ -160,6 +160,26 @@ let valueToValueType = value =>
| IEvTypeIdentifier(_) => EvtTypeIdentifier | IEvTypeIdentifier(_) => EvtTypeIdentifier
} }
let externalValueToValueType = (value: ExternalExpressionValue.t) =>
switch value {
| EvArray(_) => EvtArray
| EvArrayString(_) => EvtArrayString
| EvBool(_) => EvtBool
| EvCall(_) => EvtCall
| EvDate(_) => EvtDate
| EvDeclaration(_) => EvtDeclaration
| EvDistribution(_) => EvtDistribution
| EvLambda(_) => EvtLambda
| EvModule(_) => EvtModule
| EvNumber(_) => EvtNumber
| EvRecord(_) => EvtRecord
| EvString(_) => EvtString
| EvSymbol(_) => EvtSymbol
| EvTimeDuration(_) => EvtTimeDuration
| EvType(_) => EvtType
| EvTypeIdentifier(_) => EvtTypeIdentifier
}
let functionCallToCallSignature = (functionCall: functionCall): functionCallSignature => { let functionCallToCallSignature = (functionCall: functionCall): functionCallSignature => {
let (fn, args) = functionCall let (fn, args) = functionCall
CallSignature(fn, args->Js.Array2.map(valueToValueType)) CallSignature(fn, args->Js.Array2.map(valueToValueType))

View File

@ -631,6 +631,19 @@ module A = {
) )
let filter = Js.Array.filter let filter = Js.Array.filter
let joinWith = Js.Array.joinWith let joinWith = Js.Array.joinWith
let transpose = (xs: array<array<'a>>): array<array<'a>> => {
let arr: array<array<'a>> = []
for i in 0 to length(xs) - 1 {
for j in 0 to length(xs[i]) - 1 {
if Js.Array.length(arr) <= j {
ignore(Js.Array.push([xs[i][j]], arr))
} else {
ignore(Js.Array.push(xs[i][j], arr[j]))
}
}
}
arr
}
let all = (p: 'a => bool, xs: array<'a>): bool => length(filter(p, xs)) == length(xs) let all = (p: 'a => bool, xs: array<'a>): bool => length(filter(p, xs)) == length(xs)
let any = (p: 'a => bool, xs: array<'a>): bool => length(filter(p, xs)) > 0 let any = (p: 'a => bool, xs: array<'a>): bool => length(filter(p, xs)) > 0
@ -752,7 +765,7 @@ module A = {
let diff = (t: t): array<float> => let diff = (t: t): array<float> =>
Belt.Array.zipBy(t, Belt.Array.sliceToEnd(t, 1), (left, right) => right -. left) Belt.Array.zipBy(t, Belt.Array.sliceToEnd(t, 1), (left, right) => right -. left)
let cumsum = (t: t): array<float> => accumulate((a, b) => a +. b, t) let cumSum = (t: t): array<float> => accumulate((a, b) => a +. b, t)
let cumProd = (t: t): array<float> => accumulate((a, b) => a *. b, t) let cumProd = (t: t): array<float> => accumulate((a, b) => a *. b, t)
exception RangeError(string) exception RangeError(string)