Merge pull request #591 from quantified-uncertainty/declare-test

Declare() functionality
This commit is contained in:
Ozzie Gooen 2022-05-27 11:44:51 -04:00 committed by GitHub
commit 70664c0a91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 237 additions and 68 deletions

View File

@ -11,12 +11,36 @@ import {
defaultImports,
defaultBindings,
defaultEnvironment,
declarationArg,
declaration,
} from "@quri/squiggle-lang";
import { NumberShower } from "./NumberShower";
import { DistributionChart } from "./DistributionChart";
import { ErrorBox } from "./ErrorBox";
import { FunctionChart, FunctionChartSettings } from "./FunctionChart";
function getRange<a>(x: declaration<a>) {
let first = x.args[0];
switch (first.tag) {
case "Float": {
return { floats: { min: first.value.min, max: first.value.max } };
}
case "Date": {
return { time: { min: first.value.min, max: first.value.max } };
}
}
}
function getChartSettings<a>(x: declaration<a>): FunctionChartSettings {
let range = getRange(x);
let min = range.floats ? range.floats.min : 0;
let max = range.floats ? range.floats.max : 10;
return {
start: min,
stop: max,
count: 20,
};
}
const variableBox = {
Component: styled.div`
background: white;
@ -216,6 +240,24 @@ const SquiggleItem: React.FC<SquiggleItemProps> = ({
/>
</VariableBox>
);
case "lambdaDeclaration": {
return (
<VariableBox heading="Function Declaration" showTypes={showTypes}>
<FunctionChart
fn={expression.value.fn}
chartSettings={getChartSettings(expression.value)}
height={height}
environment={{
sampleCount: environment.sampleCount / 10,
xyPointLength: environment.xyPointLength / 10,
}}
/>
</VariableBox>
);
}
default: {
return <>Should be unreachable</>;
}
}
};

View File

@ -23,7 +23,7 @@
"tickOpacity": 0.0,
"domainColor": "#fff",
"domainOpacity": 0.0,
"format": "~g",
"format": ".9~s",
"tickCount": 10
}
],

View File

@ -20,6 +20,7 @@
"name": "x",
"type": "linear",
"nice": true,
"zero": false,
"domain": {
"data": "facet",
"field": "x"
@ -31,7 +32,7 @@
"type": "linear",
"range": "height",
"nice": true,
"zero": true,
"zero": false,
"domain": {
"data": "facet",
"field": "y"
@ -58,6 +59,7 @@
"tickOpacity": 0.0,
"domainColor": "#727d93",
"domainOpacity": 0.1,
"format": ".9~s",
"tickCount": 5
},
{
@ -69,6 +71,7 @@
"tickOpacity": 0.0,
"domainColor": "#727d93",
"domainOpacity": 0.1,
"format": ".9~s",
"tickCount": 5
}
],

View File

@ -75,6 +75,7 @@
"name": "xscale",
"type": "linear",
"nice": true,
"zero": false,
"domain": {
"data": "facet",
"field": "x"
@ -86,10 +87,10 @@
"type": "linear",
"range": "height",
"nice": true,
"zero": true,
"zero": false,
"domain": {
"data": "facet",
"field": "p99"
"fields": ["p1", "p99"]
}
}
],
@ -113,12 +114,14 @@
"tickOpacity": 0.0,
"domainColor": "#727d93",
"domainOpacity": 0.1,
"format": ".9~s",
"tickCount": 5
},
{
"orient": "left",
"scale": "yscale",
"grid": false,
"format": ".9~s",
"labelColor": "#727d93",
"tickColor": "#fff",
"tickOpacity": 0.0,

View File

@ -14,6 +14,8 @@ export {
errorValueToString,
distributionErrorToString,
distributionError,
declarationArg,
declaration,
} from "../rescript/TypescriptInterface.gen";
export type { errorValue, externalBindings as bindings, jsImports };
import {
@ -185,5 +187,7 @@ function createTsExport(
return tag("date", x.value);
case "EvTimeDuration":
return tag("timeDuration", x.value);
case "EvDeclaration":
return tag("lambdaDeclaration", x.value);
}
}

View File

@ -9,6 +9,8 @@ import {
discreteShape,
continuousShape,
lambdaValue,
lambdaDeclaration,
declarationArg,
} from "../rescript/TypescriptInterface.gen";
import { Distribution } from "./distribution";
import { tagged, tag } from "./types";
@ -63,6 +65,10 @@ export type rescriptExport =
| {
TAG: 11; // EvTimeDuration
_0: number;
}
| {
TAG: 12; // EvDeclaration
_0: rescriptLambdaDeclaration;
};
type rescriptDist =
@ -84,6 +90,23 @@ type rescriptPointSetDist =
_0: continuousShape;
};
type rescriptLambdaDeclaration = {
readonly fn: lambdaValue;
readonly args: rescriptDeclarationArg[];
};
type rescriptDeclarationArg =
| {
TAG: 0; // Float
min: number;
max: number;
}
| {
TAG: 1; // Date
min: Date;
max: Date;
};
export type squiggleExpression =
| tagged<"symbol", string>
| tagged<"string", string>
@ -96,6 +119,7 @@ export type squiggleExpression =
| tagged<"number", number>
| tagged<"date", Date>
| tagged<"timeDuration", number>
| tagged<"lambdaDeclaration", lambdaDeclaration>
| tagged<"record", { [key: string]: squiggleExpression }>;
export { lambdaValue };
@ -141,6 +165,22 @@ export function convertRawToTypescript(
return tag("date", result._0);
case 11: // EvTimeDuration
return tag("number", result._0);
case 12: // EvDeclaration
return tag("lambdaDeclaration", {
fn: result._0.fn,
args: result._0.args.map(convertDeclaration),
});
}
}
function convertDeclaration(
declarationArg: rescriptDeclarationArg
): declarationArg {
switch (declarationArg.TAG) {
case 0: // Float
return tag("Float", { min: declarationArg.min, max: declarationArg.max });
case 1: // Date
return tag("Date", { min: declarationArg.min, max: declarationArg.max });
}
}

View File

@ -8,9 +8,11 @@ type rec frType =
| FRTypeNumber
| FRTypeNumeric
| FRTypeDistOrNumber
| FRTypeLambda
| FRTypeRecord(frTypeRecord)
| FRTypeArray(array<frType>)
| FRTypeOption(frType)
| FRTypeArray(frType)
| FRTypeString
| FRTypeVariant(array<string>)
and frTypeRecord = array<frTypeRecordParam>
and frTypeRecordParam = (string, frType)
@ -21,9 +23,12 @@ and frTypeRecordParam = (string, frType)
type rec frValue =
| FRValueNumber(float)
| FRValueDist(DistributionTypes.genericDist)
| FRValueOption(option<frValue>)
| FRValueArray(array<frValue>)
| FRValueDistOrNumber(frValueDistOrNumber)
| FRValueRecord(frValueRecord)
| FRValueLambda(ReducerInterface_ExpressionValue.lambdaValue)
| FRValueString(string)
| FRValueVariant(string)
and frValueRecord = array<frValueRecordParam>
and frValueRecordParam = (string, frValue)
and frValueDistOrNumber = FRValueNumber(float) | FRValueDist(DistributionTypes.genericDist)
@ -52,8 +57,10 @@ module FRType = {
let input = ((name, frType): frTypeRecordParam) => `${name}: ${toString(frType)}`
`record({${r->E.A2.fmap(input)->E.A2.joinWith(", ")}})`
}
| FRTypeArray(r) => `record(${r->E.A2.fmap(toString)->E.A2.joinWith(", ")})`
| FRTypeOption(v) => `option(${toString(v)})`
| FRTypeArray(r) => `record(${toString(r)})`
| FRTypeLambda => `lambda`
| FRTypeString => `string`
| FRTypeVariant(_) => "variant"
}
let rec matchWithExpressionValue = (t: t, r: expressionValue): option<frValue> =>
@ -65,7 +72,11 @@ module FRType = {
| (FRTypeDistOrNumber, EvDistribution(f)) => Some(FRValueDistOrNumber(FRValueDist(f)))
| (FRTypeNumeric, EvNumber(f)) => Some(FRValueNumber(f))
| (FRTypeNumeric, EvDistribution(Symbolic(#Float(f)))) => Some(FRValueNumber(f))
| (FRTypeOption(v), _) => Some(FRValueOption(matchWithExpressionValue(v, r)))
| (FRTypeLambda, EvLambda(f)) => Some(FRValueLambda(f))
| (FRTypeArray(intendedType), EvArray(elements)) => {
let el = elements->E.A2.fmap(matchWithExpressionValue(intendedType))
E.A.O.openIfAllSome(el)->E.O2.fmap(r => FRValueArray(r))
}
| (FRTypeRecord(recordParams), EvRecord(record)) => {
let getAndMatch = (name, input) =>
E.Dict.get(record, name)->E.O.bind(matchWithExpressionValue(input))
@ -80,6 +91,23 @@ module FRType = {
| _ => None
}
let rec matchReverse = (e: frValue): expressionValue =>
switch e {
| FRValueNumber(f) => EvNumber(f)
| FRValueDistOrNumber(FRValueNumber(n)) => EvNumber(n)
| FRValueDistOrNumber(FRValueDist(n)) => EvDistribution(n)
| FRValueDist(dist) => EvDistribution(dist)
| FRValueArray(elements) => EvArray(elements->E.A2.fmap(matchReverse))
| FRValueRecord(frValueRecord) => {
let record =
frValueRecord->E.A2.fmap(((name, value)) => (name, matchReverse(value)))->E.Dict.fromArray
EvRecord(record)
}
| FRValueLambda(l) => EvLambda(l)
| FRValueString(string) => EvString(string)
| FRValueVariant(string) => EvString(string)
}
let matchWithExpressionValueArray = (inputs: array<t>, args: array<expressionValue>): option<
array<frValue>,
> => {

View File

@ -1,58 +0,0 @@
type expressionValue = ReducerInterface_ExpressionValue.expressionValue
type rec frType =
| FRTypeNumber
| FRTypeNumeric
| FRTypeDistOrNumber
| FRTypeRecord(frTypeRecord)
| FRTypeArray(array<frType>)
| FRTypeOption(frType)
and frTypeRecord = array<frTypeRecordParam>
and frTypeRecordParam = (string, frType)
type rec frValue =
| FRValueNumber(float)
| FRValueDist(DistributionTypes.genericDist)
| FRValueOption(option<frValue>)
| FRValueDistOrNumber(frValueDistOrNumber)
| FRValueRecord(frValueRecord)
and frValueRecord = array<frValueRecordParam>
and frValueRecordParam = (string, frValue)
and frValueDistOrNumber = FRValueNumber(float) | FRValueDist(DistributionTypes.genericDist)
type fnDefinition = {
name: string,
inputs: array<frType>,
run: (array<frValue>, DistributionOperation.env) => result<expressionValue, string>,
}
type function = {
name: string,
definitions: array<fnDefinition>,
}
type registry = array<function>
// Note: The function "name" is just used for documentation purposes
module Function: {
type t = function
let make: (~name: string, ~definitions: array<fnDefinition>) => t
}
module FnDefinition: {
type t = fnDefinition
let make: (
~name: string,
~inputs: array<frType>,
~run: (array<frValue>, DistributionOperation.env) => result<expressionValue, string>,
) => t
}
module Registry: {
let matchAndRun: (
~registry: registry,
~fnName: string,
~args: array<expressionValue>,
~env: QuriSquiggleLang.DistributionOperation.env,
) => option<result<expressionValue, string>>
}

View File

@ -19,6 +19,12 @@ module Prepare = {
| [FRValueRecord([(_, n1), (_, n2)])] => Ok([n1, n2])
| _ => Error(impossibleError)
}
let toArgs = (inputs: ts): result<ts, err> =>
switch inputs {
| [FRValueRecord(args)] => args->E.A2.fmap(((_, b)) => b)->Ok
| _ => Error(impossibleError)
}
}
}
@ -30,6 +36,13 @@ module Prepare = {
}
}
let twoNumbers = (values: ts): result<(float, float), err> => {
switch values {
| [FRValueNumber(a1), FRValueNumber(a2)] => Ok(a1, a2)
| _ => Error(impossibleError)
}
}
let oneDistOrNumber = (values: ts): result<frValueDistOrNumber, err> => {
switch values {
| [FRValueDistOrNumber(a1)] => Ok(a1)

View File

@ -3,7 +3,42 @@ open FunctionRegistry_Helpers
let twoArgs = E.Tuple2.toFnCall
module Declaration = {
let frType = FRTypeRecord([
("fn", FRTypeLambda),
("inputs", FRTypeArray(FRTypeRecord([("min", FRTypeNumber), ("max", FRTypeNumber)]))),
])
let fromExpressionValue = (e: frValue): result<expressionValue, 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_ExpressionValue.EvDeclaration(
Declaration.make(lambda, args),
))
}
| Error(r) => Error(r)
| Ok(_) => Error(FunctionRegistry_Helpers.impossibleError)
}
}
}
let registry = [
Function.make(
~name="FnMake",
~definitions=[
FnDefinition.make(~name="declareFn", ~inputs=[Declaration.frType], ~run=(inputs, _) => {
inputs->E.A.unsafe_get(0)->Declaration.fromExpressionValue
}),
],
),
Function.make(
~name="Normal",
~definitions=[

View File

@ -22,6 +22,7 @@ type rec expressionValue =
| EvSymbol(string)
| EvDate(Js.Date.t)
| EvTimeDuration(float)
| EvDeclaration(lambdaDeclaration)
and record = Js.Dict.t<expressionValue>
and externalBindings = record
and lambdaValue = {
@ -29,6 +30,7 @@ and lambdaValue = {
context: externalBindings,
body: internalCode,
}
and lambdaDeclaration = Declaration.declaration<lambdaValue>
@genType
let defaultExternalBindings: externalBindings = Js.Dict.empty()
@ -55,6 +57,7 @@ let rec toString = aValue =>
| EvDistribution(dist) => GenericDist.toString(dist)
| EvDate(date) => DateTime.Date.toString(date)
| EvTimeDuration(t) => DateTime.Duration.toString(t)
| EvDeclaration(d) => Declaration.toString(d, r => toString(EvLambda(r)))
}
and toStringRecord = aRecord => {
let pairs =
@ -79,6 +82,7 @@ let toStringWithType = aValue =>
| EvSymbol(_) => `Symbol::${toString(aValue)}`
| EvDate(_) => `Date::${toString(aValue)}`
| EvTimeDuration(_) => `Date::${toString(aValue)}`
| EvDeclaration(_) => `Declaration::${toString(aValue)}`
}
let argsToString = (args: array<expressionValue>): string => {
@ -124,6 +128,7 @@ type expressionValueType =
| EvtSymbol
| EvtDate
| EvtTimeDuration
| EvtDeclaration
type functionCallSignature = CallSignature(string, array<expressionValueType>)
type functionDefinitionSignature =
@ -143,6 +148,7 @@ let valueToValueType = value =>
| EvSymbol(_) => EvtSymbol
| EvDate(_) => EvtDate
| EvTimeDuration(_) => EvtTimeDuration
| EvDeclaration(_) => EvtDeclaration
}
let functionCallToCallSignature = (functionCall: functionCall): functionCallSignature => {
@ -164,6 +170,7 @@ let valueTypeToString = (valueType: expressionValueType): string =>
| EvtSymbol => `Symbol`
| EvtDate => `Date`
| EvtTimeDuration => `Duration`
| EvtDeclaration => `Declaration`
}
let functionCallSignatureToString = (functionCallSignature: functionCallSignature): string => {

View File

@ -76,6 +76,9 @@ let distributionErrorToString = DistributionTypes.Error.toString
@genType
type lambdaValue = ReducerInterface_ExpressionValue.lambdaValue
@genType
type lambdaDeclaration = ReducerInterface_ExpressionValue.lambdaDeclaration
@genType
let defaultSamplingEnv = DistributionOperation.defaultEnv
@ -87,3 +90,9 @@ let defaultEnvironment = ReducerInterface_ExpressionValue.defaultEnvironment
@genType
let foreignFunctionInterface = Reducer.foreignFunctionInterface
@genType
type declarationArg = Declaration.arg
@genType
type declaration<'a> = Declaration.declaration<'a>

View File

@ -0,0 +1,42 @@
@genType
type arg = Float({min: float, max: float}) | Date({min: Js.Date.t, max: Js.Date.t})
@genType
type declaration<'a> = {
fn: 'a,
args: array<arg>,
}
module ContinuousFloatArg = {
let make = (min: float, max: float): arg => {
Float({min: min, max: max})
}
}
module ContinuousTimeArg = {
let make = (min: Js.Date.t, max: Js.Date.t): arg => {
Date({min: min, max: max})
}
}
module Arg = {
let toString = (arg: arg) => {
switch arg {
| Float({min, max}) =>
`Float({min: ${E.Float.with2DigitsPrecision(min)}, max: ${E.Float.with2DigitsPrecision(
max,
)}})`
| Date({min, max}) =>
`Date({min: ${DateTime.Date.toString(min)}, max: ${DateTime.Date.toString(max)}})`
}
}
}
let make = (fn: 'a, args: array<arg>): declaration<'a> => {
{fn: fn, args: args}
}
let toString = (r: declaration<'a>, fnToString): string => {
let args = r.args->E.A2.fmap(Arg.toString) |> E.A.joinWith(", ")
return`fn: ${fnToString(r.fn)}, args: [${args}]`
}

View File

@ -870,4 +870,5 @@ module Dict = {
type t<'a> = Js.Dict.t<'a>
let get = Js.Dict.get
let keys = Js.Dict.keys
let fromArray = Js.Dict.fromArray
}