Merge branch 'develop' into multiple-charts
This commit is contained in:
commit
ba59047d31
|
@ -1,5 +1,10 @@
|
|||
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 { FunctionChart1Number } from "./FunctionChart1Number";
|
||||
import { DistributionPlottingSettings } from "./DistributionChart";
|
||||
|
@ -45,10 +50,16 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
|
|||
}
|
||||
};
|
||||
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":
|
||||
return (
|
||||
<FunctionChart1Dist
|
||||
|
@ -68,15 +79,11 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
|
|||
height={height}
|
||||
/>
|
||||
);
|
||||
case "Error":
|
||||
return (
|
||||
<ErrorAlert heading="Error">The function failed to be run</ErrorAlert>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<MessageAlert heading="Function Display Not Supported">
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ let getPercentiles = ({ chartSettings, fn, environment }) => {
|
|||
let chartPointsData: point[] = chartPointsToRender.map((x) => {
|
||||
let result = runForeign(fn, [x], environment);
|
||||
if (result.tag === "Ok") {
|
||||
if (result.value.tag == "distribution") {
|
||||
if (result.value.tag === "distribution") {
|
||||
return { x, value: { tag: "Ok", value: result.value.value } };
|
||||
} else {
|
||||
return {
|
||||
|
@ -166,12 +166,14 @@ export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({
|
|||
setMouseOverlay(NaN);
|
||||
}
|
||||
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
|
||||
? runForeign(fn, [mouseOverlay], environment)
|
||||
: {
|
||||
tag: "Error",
|
||||
value: {
|
||||
tag: "REExpectedType",
|
||||
tag: "RETodo",
|
||||
value: "Hover x-coordinate returned NaN. Expected a number.",
|
||||
},
|
||||
};
|
||||
|
|
|
@ -232,7 +232,7 @@ export function buildVegaSpec(
|
|||
},
|
||||
size: [{ value: 100 }],
|
||||
tooltip: {
|
||||
signal: "datum.y",
|
||||
signal: "{ probability: datum.y, value: datum.x }",
|
||||
},
|
||||
},
|
||||
update: {
|
||||
|
|
|
@ -21,6 +21,10 @@ describe("builtin", () => {
|
|||
"addOne(t)=t+1; toList(mapSamples(fromSamples([1,2,3,4,5,6]), addOne))",
|
||||
"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", () => {
|
||||
|
|
|
@ -20,7 +20,7 @@ describe("Peggy parse type", () => {
|
|||
"{(::$_typeOf_$ :f (::$_typeFunction_$ (::$_constructArray_$ (#number #number #number))))}",
|
||||
)
|
||||
})
|
||||
describe("high priority modifier", () => {
|
||||
describe("high priority contract", () => {
|
||||
testParse(
|
||||
"answer: number<-min<-max(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))))}",
|
||||
)
|
||||
})
|
||||
describe("low priority modifier", () => {
|
||||
describe("low priority contract", () => {
|
||||
testParse(
|
||||
"answer: number | string $ opaque",
|
||||
"{(::$_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_$ ()))))))}",
|
||||
)
|
||||
})
|
||||
describe("type paranthesis", () => {
|
||||
//$ is introduced to avoid paranthesis
|
||||
describe("type parenthesis", () => {
|
||||
//$ is introduced to avoid parenthesis
|
||||
testParse(
|
||||
"answer: (number|string)<-opaque",
|
||||
"{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}",
|
||||
)
|
||||
})
|
||||
describe("squiggle expressions in type modifiers", () => {
|
||||
describe("squiggle expressions in type contracts", () => {
|
||||
testParse(
|
||||
"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)))}",
|
||||
|
|
|
@ -3,11 +3,10 @@ module InternalExpressionValue = ReducerInterface_InternalExpressionValue
|
|||
|
||||
open Jest
|
||||
open Reducer_Peggy_TestHelpers
|
||||
open Expect
|
||||
|
||||
describe("Peggy to Expression", () => {
|
||||
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("'hello'", "{'hello'}", ~v="'hello'", ())
|
||||
testToExpression("true", "{true}", ~v="true", ())
|
||||
|
|
|
@ -40,7 +40,7 @@ describe("Peggy Types to Expression", () => {
|
|||
(),
|
||||
)
|
||||
})
|
||||
describe("high priority modifier", () => {
|
||||
describe("high priority contract", () => {
|
||||
testToExpression(
|
||||
"answer: number<-min(1)<-max(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(
|
||||
"answer: number | string $ opaque",
|
||||
"{(:$_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(
|
||||
"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)))}",
|
||||
|
|
|
@ -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}")
|
|
@ -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")
|
|
@ -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")
|
|
@ -10,5 +10,5 @@ describe("Evaluate ternary operator", () => {
|
|||
testEvalToBe("false ? 'YES' : 'NO'", "Ok('NO')")
|
||||
testEvalToBe("2 > 1 ? 'YES' : 'NO'", "Ok('YES')")
|
||||
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: )")
|
||||
})
|
||||
|
|
|
@ -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))
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
|
@ -117,6 +117,11 @@ let map3 = (
|
|||
): result<t, sampleSetError> =>
|
||||
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 geomean = t => T.get(t)->E.A.Floats.geomean
|
||||
let mode = t => T.get(t)->E.A.Floats.mode
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
type internalExpressionValue = ReducerInterface_InternalExpressionValue.t
|
||||
type internalExpressionValueType = ReducerInterface_InternalExpressionValue.internalExpressionValueType
|
||||
|
||||
/*
|
||||
Function Registry "Type". A type, without any other information.
|
||||
|
@ -42,18 +43,26 @@ and frValueDistOrNumber = FRValueNumber(float) | FRValueDist(DistributionTypes.g
|
|||
type fnDefinition = {
|
||||
name: string,
|
||||
inputs: array<frType>,
|
||||
run: (array<frValue>, GenericDist.env) => result<internalExpressionValue, string>,
|
||||
run: (
|
||||
array<internalExpressionValue>,
|
||||
array<frValue>,
|
||||
GenericDist.env,
|
||||
) => result<internalExpressionValue, string>,
|
||||
}
|
||||
|
||||
type function = {
|
||||
name: string,
|
||||
definitions: array<fnDefinition>,
|
||||
examples: option<string>,
|
||||
requiresNamespace: bool,
|
||||
nameSpace: string,
|
||||
output: option<internalExpressionValueType>,
|
||||
examples: array<string>,
|
||||
description: option<string>,
|
||||
isExperimental: bool,
|
||||
}
|
||||
|
||||
type registry = array<function>
|
||||
type fnNameDict = Js.Dict.t<array<function>>
|
||||
type registry = {functions: array<function>, fnNameDict: fnNameDict}
|
||||
|
||||
module FRType = {
|
||||
type t = frType
|
||||
|
@ -265,7 +274,7 @@ module Matcher = {
|
|||
|
||||
module Registry = {
|
||||
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))
|
||||
fullMatch->E.O.bind(((fn, match)) =>
|
||||
switch match {
|
||||
|
@ -276,7 +285,7 @@ module Matcher = {
|
|||
}
|
||||
|
||||
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 =
|
||||
functionMatchPairs
|
||||
->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>) => {
|
||||
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)
|
||||
| None =>
|
||||
switch _findNameMatches(r, fnName, args) {
|
||||
switch _findNameMatches(r, fnToSearch, args) {
|
||||
| Some(r) => Match.SameNameDifferentArguments(r)
|
||||
| None => Match.DifferentName
|
||||
}
|
||||
|
@ -308,7 +320,7 @@ module Matcher = {
|
|||
let matchToDef = (registry: registry, {fnName, inputIndex}: RegistryMatch.match): option<
|
||||
fnDefinition,
|
||||
> =>
|
||||
registry
|
||||
registry.functions
|
||||
->E.A.getBy(fn => fn.name === fnName)
|
||||
->E.O.bind(fn => E.A.get(fn.definitions, inputIndex))
|
||||
}
|
||||
|
@ -322,15 +334,23 @@ module FnDefinition = {
|
|||
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 argValues = FRType.matchWithExpressionValueArray(t.inputs, args)
|
||||
switch argValues {
|
||||
| Some(values) => t.run(values, env)
|
||||
| Some(values) => t.run(args, values, env)
|
||||
| None => Error("Incorrect Types")
|
||||
}
|
||||
}
|
||||
|
||||
let make = (~name, ~inputs, ~run): t => {
|
||||
let make = (~name, ~inputs, ~run, ()): t => {
|
||||
name: name,
|
||||
inputs: inputs,
|
||||
run: run,
|
||||
|
@ -343,16 +363,29 @@ module Function = {
|
|||
type functionJson = {
|
||||
name: string,
|
||||
definitions: array<string>,
|
||||
examples: option<string>,
|
||||
examples: array<string>,
|
||||
description: option<string>,
|
||||
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,
|
||||
nameSpace: nameSpace,
|
||||
definitions: definitions,
|
||||
examples: examples,
|
||||
output: output,
|
||||
examples: examples |> E.O.default([]),
|
||||
isExperimental: isExperimental,
|
||||
requiresNamespace: requiresNamespace,
|
||||
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 = {
|
||||
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
|
||||
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.
|
||||
*/
|
||||
let matchAndRun = (
|
||||
let _matchAndRun = (
|
||||
~registry: registry,
|
||||
~fnName: string,
|
||||
~args: array<internalExpressionValue>,
|
||||
~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)
|
||||
//Js.log(toSimple(registry))
|
||||
let showNameMatchDefinitions = matches => {
|
||||
let defs =
|
||||
matches
|
||||
|
@ -391,10 +466,21 @@ module Registry = {
|
|||
->E.A2.joinWith("; ")
|
||||
`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))
|
||||
| SameNameDifferentArguments(m) => Some(Error(showNameMatchDefinitions(m)))
|
||||
| _ => 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)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -213,72 +213,3 @@ module Process = {
|
|||
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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,576 +1,12 @@
|
|||
open FunctionRegistry_Core
|
||||
open FunctionRegistry_Helpers
|
||||
|
||||
let twoArgs = E.Tuple2.toFnCall
|
||||
|
||||
module Declaration = {
|
||||
let frType = FRTypeRecord([
|
||||
("fn", FRTypeLambda),
|
||||
("inputs", FRTypeArray(FRTypeRecord([("min", FRTypeNumber), ("max", FRTypeNumber)]))),
|
||||
let fnList = Belt.Array.concatMany([
|
||||
FR_Dict.library,
|
||||
FR_Dist.library,
|
||||
FR_Fn.library,
|
||||
FR_List.library,
|
||||
FR_Number.library,
|
||||
FR_Pointset.library,
|
||||
FR_Scoring.library,
|
||||
])
|
||||
|
||||
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)
|
||||
let registry = FunctionRegistry_Core.Registry.make(fnList)
|
||||
let dispatch = FunctionRegistry_Core.Registry.dispatch(registry)
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
]
|
|
@ -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
|
|
@ -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)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
]
|
|
@ -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)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
]
|
|
@ -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
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
]
|
|
@ -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))),
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
]
|
|
@ -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)
|
||||
}
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
]
|
|
@ -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 InternalExpressionValue = ReducerInterface_InternalExpressionValue
|
||||
open Reducer_ErrorValue
|
||||
|
|
|
@ -10,8 +10,8 @@ open ReducerInterface_InternalExpressionValue
|
|||
open Reducer_ErrorValue
|
||||
|
||||
/*
|
||||
MathJs provides default implementations for builtins
|
||||
This is where all the expected builtins like + = * / sin cos log ln etc are handled
|
||||
MathJs provides default implementations for built-ins
|
||||
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!
|
||||
*/
|
||||
|
||||
|
@ -149,6 +149,27 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
|
|||
doLambdaCall(aLambdaValue, list{IEvNumber(a), IEvNumber(b), IEvNumber(c)})
|
||||
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) => {
|
||||
|
@ -198,7 +219,7 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
|
|||
| ("$_typeFunction_$", [IEvArray(arr)]) => TypeBuilder.typeFunction(arr)
|
||||
| ("$_typeTuple_$", [IEvArray(elems)]) => TypeBuilder.typeTuple(elems)
|
||||
| ("$_typeArray_$", [elem]) => TypeBuilder.typeArray(elem)
|
||||
| ("$_typeRecord_$", [IEvArray(arrayOfPairs)]) => TypeBuilder.typeRecord(arrayOfPairs)
|
||||
| ("$_typeRecord_$", [IEvRecord(propertyMap)]) => TypeBuilder.typeRecord(propertyMap)
|
||||
| ("concat", [IEvArray(aValueArray), IEvArray(bValueArray)]) =>
|
||||
doAddArray(aValueArray, bValueArray)
|
||||
| ("concat", [IEvString(aValueString), IEvString(bValueString)]) =>
|
||||
|
@ -230,6 +251,8 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
|
|||
],
|
||||
) =>
|
||||
SampleMap.map3(dist1, dist2, dist3, aLambdaValue)
|
||||
| ("mapSamplesN", [IEvArray(aValueArray), IEvLambda(aLambdaValue)]) =>
|
||||
SampleMap.mapN(aValueArray, aLambdaValue)
|
||||
| ("reduce", [IEvArray(aValueArray), initialValue, IEvLambda(aLambdaValue)]) =>
|
||||
doReduceArray(aValueArray, initialValue, 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
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Reducer uses Result monad while reducing expressions
|
||||
*/
|
||||
|
@ -255,11 +277,10 @@ let dispatch = (call: functionCall, environment, reducer: ExpressionT.reducerFn)
|
|||
errorValue,
|
||||
> =>
|
||||
try {
|
||||
let callInternalWithReducer = (call, environment) => callInternal(call, environment, reducer)
|
||||
let (fn, args) = call
|
||||
// There is a bug that prevents string match in patterns
|
||||
// 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 {
|
||||
| Js.Exn.Error(obj) => REJavaScriptExn(Js.Exn.message(obj), Js.Exn.name(obj))->Error
|
||||
| _ => RETodo("unhandled rescript exception")->Error
|
||||
|
|
|
@ -144,7 +144,7 @@ let dispatchMacroCall = (
|
|||
let ifTrueBlock = eBlock(list{ifTrue})
|
||||
ExpressionWithContext.withContext(ifTrueBlock, bindings)->Ok
|
||||
}
|
||||
| _ => REExpectedType("Boolean")->Error
|
||||
| _ => REExpectedType("Boolean", "")->Error
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ type errorValue =
|
|||
| REArrayIndexNotFound(string, int)
|
||||
| REAssignmentExpected
|
||||
| REDistributionError(DistributionTypes.error)
|
||||
| REExpectedType(string)
|
||||
| REExpectedType(string, string)
|
||||
| REExpressionExpected
|
||||
| REFunctionExpected(string)
|
||||
| REFunctionNotFound(string)
|
||||
|
@ -55,6 +55,6 @@ let errorToString = err =>
|
|||
| RESymbolNotFound(symbolName) => `${symbolName} is not defined`
|
||||
| RESyntaxError(desc, _) => `Syntax Error: ${desc}`
|
||||
| RETodo(msg) => `TODO: ${msg}`
|
||||
| REExpectedType(typeName) => `Expected type: ${typeName}`
|
||||
| REExpectedType(typeName, valueString) => `Expected type: ${typeName} but got: ${valueString}`
|
||||
| REUnitNotFound(unitName) => `Unit not found: ${unitName}`
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
exception ImpossibleException
|
||||
exception ImpossibleException(string)
|
||||
|
|
|
@ -30,12 +30,12 @@ let rec toString = expression =>
|
|||
switch expression {
|
||||
| EList(list{EValue(IEvCall("$$_block_$$")), ...statements}) =>
|
||||
`{${Belt.List.map(statements, aValue => toString(aValue))
|
||||
->Extra.List.interperse("; ")
|
||||
->Extra.List.intersperse("; ")
|
||||
->Belt.List.toArray
|
||||
->Js.String.concatMany("")}}`
|
||||
| EList(aList) =>
|
||||
`(${Belt.List.map(aList, aValue => toString(aValue))
|
||||
->Extra.List.interperse(" ")
|
||||
->Extra.List.intersperse(" ")
|
||||
->Belt.List.toArray
|
||||
->Js.String.concatMany("")})`
|
||||
| EValue(aValue) => InternalExpressionValue.toString(aValue)
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
*/
|
||||
module ExtraList = Reducer_Extra_List
|
||||
|
||||
let interperse = (anArray, seperator) =>
|
||||
anArray->Belt.List.fromArray->ExtraList.interperse(seperator)->Belt.List.toArray
|
||||
let intersperse = (anArray, seperator) =>
|
||||
anArray->Belt.List.fromArray->ExtraList.intersperse(seperator)->Belt.List.toArray
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/*
|
||||
Insert seperator between the elements of a list
|
||||
*/
|
||||
let rec interperse = (aList, seperator) =>
|
||||
let rec intersperse = (aList, seperator) =>
|
||||
switch aList {
|
||||
| list{} => list{}
|
||||
| list{a} => list{a}
|
||||
| list{a, ...rest} => list{a, seperator, ...interperse(rest, seperator)}
|
||||
| list{a, ...rest} => list{a, seperator, ...intersperse(rest, seperator)}
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ let rec pgToString = (peggyNode: peggyNode): string => {
|
|||
args->Js.Array2.map(arg => PgNodeIdentifier(arg)->pgToString)->Js.Array2.toString
|
||||
|
||||
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 {
|
||||
| PgNodeBlock(node) => "{" ++ node["statements"]->nodesToStringUsingSeparator("; ") ++ "}"
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
)
|
||||
}
|
|
@ -3,13 +3,42 @@ open InternalExpressionValue
|
|||
|
||||
type rec iType =
|
||||
| ItTypeIdentifier(string)
|
||||
| ItModifiedType({modifiedType: iType})
|
||||
| ItModifiedType({modifiedType: iType, contracts: Belt.Map.String.t<InternalExpressionValue.t>})
|
||||
| ItTypeOr({typeOr: array<iType>})
|
||||
| ItTypeFunction({inputs: array<iType>, output: iType})
|
||||
| ItTypeArray({element: iType})
|
||||
| ItTypeTuple({elements: array<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 default = IEvString("")
|
||||
let evTypeTag: InternalExpressionValue.t = Belt.Map.String.getWithDefault(
|
||||
|
@ -52,31 +81,39 @@ let rec fromTypeMap = typeMap => {
|
|||
"properties",
|
||||
default,
|
||||
)
|
||||
//TODO: map type modifiers
|
||||
switch evTypeTag {
|
||||
| IEvString("typeIdentifier") => ItModifiedType({modifiedType: fromIEvValue(evTypeIdentifier)})
|
||||
|
||||
let contracts =
|
||||
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("typeFunction") =>
|
||||
ItTypeFunction({inputs: fromIEvArray(evInputs), output: fromIEvValue(evOutput)})
|
||||
| IEvString("typeArray") => ItTypeArray({element: fromIEvValue(evElement)})
|
||||
| IEvString("typeTuple") => ItTypeTuple({elements: fromIEvArray(evElements)})
|
||||
| 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 {
|
||||
| IEvTypeIdentifier(typeIdentifier) => ItTypeIdentifier({typeIdentifier})
|
||||
| IEvType(typeMap) => fromTypeMap(typeMap)
|
||||
| _ => raise(Reducer_Exception.ImpossibleException)
|
||||
| _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_T-ievValue"))
|
||||
}
|
||||
and fromIEvArray = (ievArray: InternalExpressionValue.t) =>
|
||||
switch ievArray {
|
||||
| IEvArray(array) => array->Belt.Array.map(fromIEvValue)
|
||||
| _ => raise(Reducer_Exception.ImpossibleException)
|
||||
| _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_T-ievArray"))
|
||||
}
|
||||
and fromIEvRecord = (ievRecord: InternalExpressionValue.t) =>
|
||||
switch ievRecord {
|
||||
| IEvRecord(record) => record->Belt.Map.String.map(fromIEvValue)
|
||||
| _ => raise(Reducer_Exception.ImpossibleException)
|
||||
| _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_T-ievRecord"))
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ let typeFunction = anArray => {
|
|||
|
||||
let typeArray = element => {
|
||||
let newRecord = Belt.Map.String.fromArray([
|
||||
("typeTag", IEvString("typeTuple")),
|
||||
("typeTag", IEvString("typeArray")),
|
||||
("element", element),
|
||||
])
|
||||
newRecord->IEvType->Ok
|
||||
|
@ -64,22 +64,14 @@ let typeArray = element => {
|
|||
|
||||
let typeTuple = anArray => {
|
||||
let newRecord = Belt.Map.String.fromArray([
|
||||
("typeTag", IEvString("typeArray")),
|
||||
("typeTag", IEvString("typeTuple")),
|
||||
("elements", IEvArray(anArray)),
|
||||
])
|
||||
newRecord->IEvType->Ok
|
||||
}
|
||||
|
||||
let typeRecord = arrayOfPairs => {
|
||||
let newProperties =
|
||||
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 typeRecord = propertyMap => {
|
||||
let newProperties = propertyMap->IEvRecord
|
||||
let newRecord = Belt.Map.String.fromArray([
|
||||
("typeTag", IEvString("typeRecord")),
|
||||
("properties", newProperties),
|
||||
|
|
|
@ -1,81 +1,168 @@
|
|||
module ExpressionT = Reducer_Expression_T
|
||||
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
|
||||
module T = Reducer_Type_T
|
||||
module TypeBuilder = Reducer_Type_TypeBuilder
|
||||
module TypeContracts = Reducer_Type_Contracts
|
||||
open InternalExpressionValue
|
||||
|
||||
type 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 rec isITypeOf = (anIType: T.iType, aValue): result<bool, T.typeErrorValue> => {
|
||||
let caseTypeIdentifier = (anUpperTypeName, aValue) => {
|
||||
let aTypeName = anUpperTypeName->Js.String2.toLowerCase
|
||||
let valueTypeName = aValue->valueToValueType->valueTypeToString->Js.String2.toLowerCase
|
||||
switch aTypeName === valueTypeName {
|
||||
switch aTypeName == valueTypeName {
|
||||
| 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) =>
|
||||
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(map, property) {
|
||||
| Some(propertyValue) => isOfResolvedIType(propertyType, propertyValue)
|
||||
| None => TypeErrorWithProperty(anIType, evValue, property)->Error
|
||||
switch Belt.Map.String.get(aRecord, property) {
|
||||
| Some(propertyValue) => isITypeOf(propertyType, propertyValue)
|
||||
| None => T.TypeMismatch(anIType, evValue)->Error
|
||||
}
|
||||
)
|
||||
})
|
||||
} else {
|
||||
T.TypeMismatch(anIType, evValue)->Error
|
||||
}
|
||||
let _caseArray = (anIType, evValue, elementType, anArray) => {
|
||||
Belt.Array.reduceWithIndex(anArray, Ok(true), (acc, element, index) => {
|
||||
switch isOfResolvedIType(elementType, element) {
|
||||
|
||||
| _ => 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(_) => TypeErrorWithPosition(anIType, evValue, index)->Error
|
||||
| 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, _ =>
|
||||
switch acc {
|
||||
| Ok(false) =>
|
||||
switch isITypeOf(anIType, evValue) {
|
||||
| Ok(_) => Ok(true)
|
||||
| Error(_) => acc
|
||||
}
|
||||
| _ => acc
|
||||
}
|
||||
)
|
||||
) {
|
||||
| Ok(true) => Ok(true)
|
||||
| Ok(false) => T.TypeMismatch(anIType, evValue)->Error
|
||||
| Error(error) => Error(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 {
|
||||
| ItTypeIdentifier(name) => caseTypeIdentifier(name, aValue)
|
||||
// TODO: Work in progress. Code is commented to make an a release of other features
|
||||
// | ItModifiedType({modifiedType: anIType}) => raise(Reducer_Exception.ImpossibleException)
|
||||
// | ItTypeOr({typeOr: anITypeArray}) => raise(Reducer_Exception.ImpossibleException)
|
||||
// | ItTypeFunction({inputs: anITypeArray, output: anIType}) =>
|
||||
// raise(Reducer_Exception.ImpossibleException)
|
||||
// | ItTypeArray({element: anIType}) => raise(Reducer_Exception.ImpossibleException)
|
||||
// | ItTypeTuple({elements: anITypeArray}) => raise(Reducer_Exception.ImpossibleException)
|
||||
// | ItTypeRecord({properties: anITypeMap}) => raise(Reducer_Exception.ImpossibleException)
|
||||
| _ => raise(Reducer_Exception.ImpossibleException)
|
||||
| ItModifiedType({modifiedType, contracts}) =>
|
||||
caseModifiedType(anIType, modifiedType, contracts, aValue) //{modifiedType: iType, contracts: Belt.Map.String.t<InternalExpressionValue.t>}
|
||||
| ItTypeOr({typeOr}) => caseOr(anIType, typeOr, aValue)
|
||||
| ItTypeFunction(_) =>
|
||||
raise(
|
||||
Reducer_Exception.ImpossibleException(
|
||||
"Reducer_TypeChecker-functions are without a type at the moment",
|
||||
),
|
||||
)
|
||||
| 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> =>
|
||||
aType->T.fromIEvValue->isOfResolvedIType(aValue)
|
||||
let isTypeOf = (
|
||||
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 checkArguments = (
|
||||
// evFunctionType: InternalExpressionValue.t,
|
||||
// args: array<InternalExpressionValue.t>,
|
||||
// ) => {
|
||||
// let functionType = switch evFunctionType {
|
||||
// | IEvRecord(functionType) => functionType
|
||||
// | _ => 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 checkITypeArguments = (anIType: T.iType, args: array<InternalExpressionValue.t>): result<
|
||||
bool,
|
||||
T.typeErrorValue,
|
||||
> => {
|
||||
switch anIType {
|
||||
| T.ItTypeFunction({inputs}) => isITypeOf(T.ItTypeTuple({elements: inputs}), args->IEvArray)
|
||||
| _ => T.TypeMismatch(anIType, args->IEvArray)->Error
|
||||
}
|
||||
}
|
||||
|
||||
// let compileTypeExpression = (typeExpression: string, bindings: ExpressionT.bindings, reducerFn: ExpressionT.reducerFn) => {
|
||||
// statement = `type compiled=${typeExpression}`
|
||||
|
||||
// }
|
||||
|
||||
//TODO: asGuard
|
||||
let checkArguments = (
|
||||
typeExpressionSourceCode: string,
|
||||
args: array<InternalExpressionValue.t>,
|
||||
reducerFn: ExpressionT.reducerFn,
|
||||
): result<InternalExpressionValue.t, ErrorValue.t> => {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +1,10 @@
|
|||
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
// 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<
|
||||
let dispatch = (call: InternalExpressionValue.functionCall, environment, reducer, chain): result<
|
||||
internalExpressionValue,
|
||||
'e,
|
||||
> => {
|
||||
|
@ -32,9 +13,10 @@ let dispatch = (call: InternalExpressionValue.functionCall, environment, chain):
|
|||
() => ReducerInterface_Date.dispatch(call, environment),
|
||||
() => ReducerInterface_Duration.dispatch(call, environment),
|
||||
() => ReducerInterface_Number.dispatch(call, environment),
|
||||
() => tryRegistry(call, environment),
|
||||
])->E.O2.default(chain(call, environment))
|
||||
() => FunctionRegistry_Library.dispatch(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.
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ let toStringWithType = aValue =>
|
|||
| IEvDeclaration(_) => `Declaration::${toString(aValue)}`
|
||||
| IEvDistribution(_) => `Distribution::${toString(aValue)}`
|
||||
| IEvLambda(_) => `Lambda::${toString(aValue)}`
|
||||
| IEvBindings(_) => `Module::${toString(aValue)}`
|
||||
| IEvBindings(_) => `Bindings::${toString(aValue)}`
|
||||
| IEvNumber(_) => `Number::${toString(aValue)}`
|
||||
| IEvRecord(_) => `Record::${toString(aValue)}`
|
||||
| IEvString(_) => `String::${toString(aValue)}`
|
||||
|
@ -160,6 +160,26 @@ let valueToValueType = value =>
|
|||
| 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 (fn, args) = functionCall
|
||||
CallSignature(fn, args->Js.Array2.map(valueToValueType))
|
||||
|
|
|
@ -631,6 +631,19 @@ module A = {
|
|||
)
|
||||
let filter = Js.Array.filter
|
||||
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 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> =>
|
||||
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)
|
||||
|
||||
exception RangeError(string)
|
||||
|
|
Loading…
Reference in New Issue
Block a user