Merge branch 'develop' into multiple-charts

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

View File

@ -1,5 +1,10 @@
import * as React from "react";
import { 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>
);
}

View File

@ -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.",
},
};

View File

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

View File

@ -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", () => {

View File

@ -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)))}",

View File

@ -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", ())

View File

@ -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)))}",

View File

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

View File

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

View File

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

View File

@ -10,5 +10,5 @@ describe("Evaluate ternary operator", () => {
testEvalToBe("false ? 'YES' : 'NO'", "Ok('NO')")
testEvalToBe("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: )")
})

View File

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

View File

@ -117,6 +117,11 @@ let map3 = (
): result<t, sampleSetError> =>
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

View File

@ -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)),
)
}
}

View File

@ -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)
})
}

View File

@ -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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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

View File

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

View File

@ -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}`
}

View File

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

View File

@ -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)

View File

@ -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

View File

@ -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)}
}

View File

@ -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("; ") ++ "}"

View File

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

View File

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

View File

@ -3,13 +3,42 @@ open InternalExpressionValue
type rec iType =
| 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"))
}

View File

@ -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),

View File

@ -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
}
}

View File

@ -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.

View File

@ -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))

View File

@ -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)