diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index f07f5cc8..f33ccb84 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -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(x: declaration) { + 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(x: declaration): 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 = ({ /> ); + case "lambdaDeclaration": { + return ( + + + + ); + } + default: { + return <>Should be unreachable; + } } }; diff --git a/packages/components/src/vega-specs/spec-distributions.json b/packages/components/src/vega-specs/spec-distributions.json index 29ca4e1d..cb3db4b8 100644 --- a/packages/components/src/vega-specs/spec-distributions.json +++ b/packages/components/src/vega-specs/spec-distributions.json @@ -23,7 +23,7 @@ "tickOpacity": 0.0, "domainColor": "#fff", "domainOpacity": 0.0, - "format": "~g", + "format": ".9~s", "tickCount": 10 } ], diff --git a/packages/components/src/vega-specs/spec-line-chart.json b/packages/components/src/vega-specs/spec-line-chart.json index 117d9543..6180ad76 100644 --- a/packages/components/src/vega-specs/spec-line-chart.json +++ b/packages/components/src/vega-specs/spec-line-chart.json @@ -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 } ], diff --git a/packages/components/src/vega-specs/spec-percentiles.json b/packages/components/src/vega-specs/spec-percentiles.json index d533a866..415cc173 100644 --- a/packages/components/src/vega-specs/spec-percentiles.json +++ b/packages/components/src/vega-specs/spec-percentiles.json @@ -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, diff --git a/packages/squiggle-lang/src/js/index.ts b/packages/squiggle-lang/src/js/index.ts index 8b2a46c0..03068a51 100644 --- a/packages/squiggle-lang/src/js/index.ts +++ b/packages/squiggle-lang/src/js/index.ts @@ -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); } } diff --git a/packages/squiggle-lang/src/js/rescript_interop.ts b/packages/squiggle-lang/src/js/rescript_interop.ts index 7bb98b5d..9f9bdb39 100644 --- a/packages/squiggle-lang/src/js/rescript_interop.ts +++ b/packages/squiggle-lang/src/js/rescript_interop.ts @@ -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 }); } } diff --git a/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Core.res b/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Core.res index 99ecc78f..eed48ffc 100644 --- a/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Core.res +++ b/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Core.res @@ -8,9 +8,11 @@ type rec frType = | FRTypeNumber | FRTypeNumeric | FRTypeDistOrNumber + | FRTypeLambda | FRTypeRecord(frTypeRecord) - | FRTypeArray(array) - | FRTypeOption(frType) + | FRTypeArray(frType) + | FRTypeString + | FRTypeVariant(array) and frTypeRecord = array and frTypeRecordParam = (string, frType) @@ -21,9 +23,12 @@ and frTypeRecordParam = (string, frType) type rec frValue = | FRValueNumber(float) | FRValueDist(DistributionTypes.genericDist) - | FRValueOption(option) + | FRValueArray(array) | FRValueDistOrNumber(frValueDistOrNumber) | FRValueRecord(frValueRecord) + | FRValueLambda(ReducerInterface_ExpressionValue.lambdaValue) + | FRValueString(string) + | FRValueVariant(string) and frValueRecord = array 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 => @@ -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, args: array): option< array, > => { diff --git a/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Core.resi b/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Core.resi deleted file mode 100644 index 5ca8c708..00000000 --- a/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Core.resi +++ /dev/null @@ -1,58 +0,0 @@ -type expressionValue = ReducerInterface_ExpressionValue.expressionValue - -type rec frType = - | FRTypeNumber - | FRTypeNumeric - | FRTypeDistOrNumber - | FRTypeRecord(frTypeRecord) - | FRTypeArray(array) - | FRTypeOption(frType) -and frTypeRecord = array -and frTypeRecordParam = (string, frType) - -type rec frValue = - | FRValueNumber(float) - | FRValueDist(DistributionTypes.genericDist) - | FRValueOption(option) - | FRValueDistOrNumber(frValueDistOrNumber) - | FRValueRecord(frValueRecord) -and frValueRecord = array -and frValueRecordParam = (string, frValue) -and frValueDistOrNumber = FRValueNumber(float) | FRValueDist(DistributionTypes.genericDist) - -type fnDefinition = { - name: string, - inputs: array, - run: (array, DistributionOperation.env) => result, -} - -type function = { - name: string, - definitions: array, -} - -type registry = array - -// Note: The function "name" is just used for documentation purposes -module Function: { - type t = function - let make: (~name: string, ~definitions: array) => t -} - -module FnDefinition: { - type t = fnDefinition - let make: ( - ~name: string, - ~inputs: array, - ~run: (array, DistributionOperation.env) => result, - ) => t -} - -module Registry: { - let matchAndRun: ( - ~registry: registry, - ~fnName: string, - ~args: array, - ~env: QuriSquiggleLang.DistributionOperation.env, - ) => option> -} diff --git a/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Helpers.res b/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Helpers.res index e9b7f11c..1b0fa809 100644 --- a/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Helpers.res +++ b/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Helpers.res @@ -19,6 +19,12 @@ module Prepare = { | [FRValueRecord([(_, n1), (_, n2)])] => Ok([n1, n2]) | _ => Error(impossibleError) } + + let toArgs = (inputs: ts): result => + 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 => { switch values { | [FRValueDistOrNumber(a1)] => Ok(a1) diff --git a/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Library.res b/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Library.res index 43be7118..532f534d 100644 --- a/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Library.res +++ b/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Library.res @@ -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 => { + 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=[ diff --git a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res index c9363606..aa040fc2 100644 --- a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res +++ b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res @@ -22,6 +22,7 @@ type rec expressionValue = | EvSymbol(string) | EvDate(Js.Date.t) | EvTimeDuration(float) + | EvDeclaration(lambdaDeclaration) and record = Js.Dict.t and externalBindings = record and lambdaValue = { @@ -29,6 +30,7 @@ and lambdaValue = { context: externalBindings, body: internalCode, } +and lambdaDeclaration = Declaration.declaration @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): string => { @@ -124,6 +128,7 @@ type expressionValueType = | EvtSymbol | EvtDate | EvtTimeDuration + | EvtDeclaration type functionCallSignature = CallSignature(string, array) 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 => { diff --git a/packages/squiggle-lang/src/rescript/TypescriptInterface.res b/packages/squiggle-lang/src/rescript/TypescriptInterface.res index 93af9832..932edaa1 100644 --- a/packages/squiggle-lang/src/rescript/TypescriptInterface.res +++ b/packages/squiggle-lang/src/rescript/TypescriptInterface.res @@ -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> diff --git a/packages/squiggle-lang/src/rescript/Utility/Declaration.res b/packages/squiggle-lang/src/rescript/Utility/Declaration.res new file mode 100644 index 00000000..871dd580 --- /dev/null +++ b/packages/squiggle-lang/src/rescript/Utility/Declaration.res @@ -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, +} + +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): 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}]` +} diff --git a/packages/squiggle-lang/src/rescript/Utility/E.res b/packages/squiggle-lang/src/rescript/Utility/E.res index 2c45a8b2..40852eb3 100644 --- a/packages/squiggle-lang/src/rescript/Utility/E.res +++ b/packages/squiggle-lang/src/rescript/Utility/E.res @@ -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 }