diff --git a/packages/components/package.json b/packages/components/package.json index de8efb96..3b50afbf 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,19 +1,19 @@ { "name": "@quri/squiggle-components", - "version": "0.2.14", + "version": "0.2.15", "license": "MIT", "dependencies": { - "react-ace": "10.1.0", "@quri/squiggle-lang": "^0.2.7", - "react-dom": "^18.1.0", - "vega": "^5.22.1", - "vega-embed": "^6.20.6", - "vega-lite": "^5.2.0", - "react-vega": "^7.5.0", - "react": "^18.1.0", "@react-hook/size": "^2.1.2", "lodash": "^4.17.21", - "styled-components": "^5.3.5" + "react": "^18.1.0", + "react-ace": "10.1.0", + "react-dom": "^18.1.0", + "react-vega": "^7.5.0", + "styled-components": "^5.3.5", + "vega": "^5.22.1", + "vega-embed": "^6.20.6", + "vega-lite": "^5.2.0" }, "devDependencies": { "@babel/plugin-proposal-private-property-in-object": "^7.16.7", @@ -25,27 +25,26 @@ "@storybook/node-logger": "^6.4.22", "@storybook/preset-create-react-app": "^4.1.0", "@storybook/react": "^6.4.22", - "@types/styled-components": "^5.1.24", - "@types/webpack": "^5.28.0", - "style-loader": "^3.3.1", - "ts-loader": "^9.2.9", - "webpack": "^5.72.0", - "webpack-cli": "^4.9.2", - "webpack-dev-server": "^4.8.1", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.1.1", "@testing-library/user-event": "^14.1.1", "@types/jest": "^27.4.0", - "web-vitals": "^2.1.4", "@types/lodash": "^4.14.182", "@types/node": "^17.0.29", "@types/react": "^18.0.3", "@types/react-dom": "^18.0.2", + "@types/styled-components": "^5.1.24", + "@types/webpack": "^5.28.0", "cross-env": "^7.0.3", "react-scripts": "5.0.1", + "style-loader": "^3.3.1", + "ts-loader": "^9.2.9", "tsconfig-paths-webpack-plugin": "^3.5.2", "typescript": "^4.6.3", - "webpack-cli": "^4.9.2" + "web-vitals": "^2.1.4", + "webpack": "^5.72.0", + "webpack-cli": "^4.9.2", + "webpack-dev-server": "^4.8.1" }, "scripts": { "start": "cross-env REACT_APP_FAST_REFRESH=false && start-storybook -p 6006 -s public", diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index 8730beaa..31cd9787 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -3,16 +3,17 @@ import _ from "lodash"; import styled from "styled-components"; import { run, - runPartial, errorValueToString, squiggleExpression, bindings, samplingParams, + jsImports, + defaultImports, + defaultBindings, } from "@quri/squiggle-lang"; import { NumberShower } from "./NumberShower"; import { DistributionChart } from "./DistributionChart"; import { ErrorBox } from "./ErrorBox"; -import useSize from "@react-hook/size"; const variableBox = { Component: styled.div` @@ -152,6 +153,8 @@ export interface SquiggleChartProps { height?: number; /** Bindings of previous variables declared */ bindings?: bindings; + /** JS imported parameters */ + jsImports?: jsImports; } const ChartWrapper = styled.div` @@ -166,14 +169,20 @@ export const SquiggleChart: React.FC = ({ outputXYPoints = 1000, onChange = () => {}, height = 60, - bindings = {}, + bindings = defaultBindings, + jsImports = defaultImports, width = NaN, }: SquiggleChartProps) => { let samplingInputs: samplingParams = { sampleCount: sampleCount, xyPointLength: outputXYPoints, }; - let expressionResult = run(squiggleString, bindings, samplingInputs); + let expressionResult = run( + squiggleString, + bindings, + samplingInputs, + jsImports + ); let internal: JSX.Element; if (expressionResult.tag === "Ok") { let expression = expressionResult.value; diff --git a/packages/components/src/components/SquiggleEditor.tsx b/packages/components/src/components/SquiggleEditor.tsx index f85cf1e8..2c01f72f 100644 --- a/packages/components/src/components/SquiggleEditor.tsx +++ b/packages/components/src/components/SquiggleEditor.tsx @@ -3,8 +3,18 @@ import * as ReactDOM from "react-dom"; import { SquiggleChart } from "./SquiggleChart"; import { CodeEditor } from "./CodeEditor"; import styled from "styled-components"; -import type { squiggleExpression, bindings } from "@quri/squiggle-lang"; -import { runPartial, errorValueToString } from "@quri/squiggle-lang"; +import type { + squiggleExpression, + samplingParams, + bindings, + jsImports, +} from "@quri/squiggle-lang"; +import { + runPartial, + errorValueToString, + defaultImports, + defaultBindings, +} from "@quri/squiggle-lang"; import { ErrorBox } from "./ErrorBox"; export interface SquiggleEditorProps { @@ -30,6 +40,8 @@ export interface SquiggleEditorProps { width: number; /** Previous variable declarations */ bindings: bindings; + /** JS Imports */ + jsImports: jsImports; } const Input = styled.div` @@ -50,7 +62,8 @@ export let SquiggleEditor: React.FC = ({ diagramCount, onChange, environment, - bindings = {}, + bindings = defaultBindings, + jsImports = defaultImports, }: SquiggleEditorProps) => { let [expression, setExpression] = React.useState(initialSquiggleString); return ( @@ -77,6 +90,7 @@ export let SquiggleEditor: React.FC = ({ environment={environment} onChange={onChange} bindings={bindings} + jsImports={jsImports} /> ); @@ -134,16 +148,30 @@ export interface SquigglePartialProps { /** The width of the element */ width: number; /** Previously declared variables */ - bindings: bindings; + bindings?: bindings; + /** Variables imported from js */ + jsImports?: jsImports; } export let SquigglePartial: React.FC = ({ initialSquiggleString = "", onChange, - bindings, + bindings = defaultBindings, + sampleCount = 1000, + outputXYPoints = 1000, + jsImports = defaultImports, }: SquigglePartialProps) => { + let samplingInputs: samplingParams = { + sampleCount: sampleCount, + xyPointLength: outputXYPoints, + }; let [expression, setExpression] = React.useState(initialSquiggleString); - let squiggleResult = runPartial(expression, bindings); + let squiggleResult = runPartial( + expression, + bindings, + samplingInputs, + jsImports + ); if (squiggleResult.tag == "Ok") { if (onChange) onChange(squiggleResult.value); } diff --git a/packages/components/src/vega-specs/spec-distributions.json b/packages/components/src/vega-specs/spec-distributions.json index 129183a5..5b6ed261 100644 --- a/packages/components/src/vega-specs/spec-distributions.json +++ b/packages/components/src/vega-specs/spec-distributions.json @@ -88,7 +88,7 @@ "tickOpacity": 0.0, "domainColor": "#fff", "domainOpacity": 0.0, - "format": "~s", + "format": "~g", "tickCount": 10 } ], diff --git a/packages/squiggle-lang/__tests__/TS/JS_test.ts b/packages/squiggle-lang/__tests__/TS/JS_test.ts index e522eb95..76871fc8 100644 --- a/packages/squiggle-lang/__tests__/TS/JS_test.ts +++ b/packages/squiggle-lang/__tests__/TS/JS_test.ts @@ -1,4 +1,4 @@ -import { Distribution, resultMap } from "../../src/js/index"; +import { Distribution, resultMap, defaultBindings } from "../../src/js/index"; import { testRun, testRunPartial } from "./TestHelpers"; function Ok(x: b) { @@ -68,6 +68,28 @@ describe("Partials", () => { }); }); +describe("JS Imports", () => { + test("Can pass parameters into partials and cells", () => { + let bindings = testRunPartial(`y = $x + 2`, defaultBindings, { x: 1 }); + let bindings2 = testRunPartial(`z = y + $a`, bindings, { a: 3 }); + expect(testRun(`z`, bindings2)).toEqual({ + tag: "number", + value: 6, + }); + }); + test("Complicated deep parameters", () => { + expect( + testRun(`$x.y[0][0].w + $x.z + $u.v`, defaultBindings, { + x: { y: [[{ w: 1 }]], z: 2 }, + u: { v: 3 }, + }) + ).toEqual({ + tag: "number", + value: 6, + }); + }); +}); + describe("Distribution", () => { //It's important that sampleCount is less than 9. If it's more, than that will create randomness //Also, note, the value should be created using makeSampleSetDist() later on. diff --git a/packages/squiggle-lang/__tests__/TS/TestHelpers.ts b/packages/squiggle-lang/__tests__/TS/TestHelpers.ts index 7d51c98e..d9d8444f 100644 --- a/packages/squiggle-lang/__tests__/TS/TestHelpers.ts +++ b/packages/squiggle-lang/__tests__/TS/TestHelpers.ts @@ -4,14 +4,25 @@ import { bindings, squiggleExpression, errorValueToString, + defaultImports, + defaultBindings, + jsImports, } from "../../src/js/index"; -export function testRun(x: string, bindings = {}): squiggleExpression { - let squiggleResult = run(x, bindings, { - sampleCount: 1000, - xyPointLength: 100, - }); - // return squiggleResult.value +export function testRun( + x: string, + bindings: bindings = defaultBindings, + imports: jsImports = defaultImports +): squiggleExpression { + let squiggleResult = run( + x, + bindings, + { + sampleCount: 1000, + xyPointLength: 100, + }, + imports + ); if (squiggleResult.tag === "Ok") { return squiggleResult.value; } else { @@ -23,11 +34,20 @@ export function testRun(x: string, bindings = {}): squiggleExpression { } } -export function testRunPartial(x: string, bindings: bindings = {}): bindings { - let squiggleResult = runPartial(x, bindings, { - sampleCount: 1000, - xyPointLength: 100, - }); +export function testRunPartial( + x: string, + bindings: bindings = defaultBindings, + imports: jsImports = defaultImports +): bindings { + let squiggleResult = runPartial( + x, + bindings, + { + sampleCount: 1000, + xyPointLength: 100, + }, + imports + ); if (squiggleResult.tag === "Ok") { return squiggleResult.value; } else { diff --git a/packages/squiggle-lang/src/js/distribution.ts b/packages/squiggle-lang/src/js/distribution.ts new file mode 100644 index 00000000..44c15882 --- /dev/null +++ b/packages/squiggle-lang/src/js/distribution.ts @@ -0,0 +1,247 @@ +import * as _ from "lodash"; +import { + genericDist, + continuousShape, + discreteShape, + environment, + distributionError, + toPointSet, + distributionErrorToString, +} from "../rescript/TypescriptInterface.gen"; +import { result, resultMap, Ok } from "./types"; +import { + Constructors_mean, + Constructors_sample, + Constructors_pdf, + Constructors_cdf, + Constructors_inv, + Constructors_normalize, + Constructors_isNormalized, + Constructors_toPointSet, + Constructors_toSampleSet, + Constructors_truncate, + Constructors_inspect, + Constructors_toString, + Constructors_toSparkline, + Constructors_algebraicAdd, + Constructors_algebraicMultiply, + Constructors_algebraicDivide, + Constructors_algebraicSubtract, + Constructors_algebraicLogarithm, + Constructors_algebraicPower, + Constructors_pointwiseAdd, + Constructors_pointwiseMultiply, + Constructors_pointwiseDivide, + Constructors_pointwiseSubtract, + Constructors_pointwiseLogarithm, + Constructors_pointwisePower, +} from "../rescript/Distributions/DistributionOperation/DistributionOperation.gen"; + +export type point = { x: number; y: number }; + +function shapePoints(x: continuousShape | discreteShape): point[] { + let xs = x.xyShape.xs; + let ys = x.xyShape.ys; + return _.zipWith(xs, ys, (x, y) => ({ x, y })); +} +export type shape = { + continuous: point[]; + discrete: point[]; +}; + +export class Distribution { + t: genericDist; + env: environment; + + constructor(t: genericDist, env: environment) { + this.t = t; + this.env = env; + return this; + } + + mapResultDist( + r: result + ): result { + return resultMap(r, (v: genericDist) => new Distribution(v, this.env)); + } + + mean(): result { + return Constructors_mean({ env: this.env }, this.t); + } + + sample(): result { + return Constructors_sample({ env: this.env }, this.t); + } + + pdf(n: number): result { + return Constructors_pdf({ env: this.env }, this.t, n); + } + + cdf(n: number): result { + return Constructors_cdf({ env: this.env }, this.t, n); + } + + inv(n: number): result { + return Constructors_inv({ env: this.env }, this.t, n); + } + + isNormalized(): result { + return Constructors_isNormalized({ env: this.env }, this.t); + } + + normalize(): result { + return this.mapResultDist( + Constructors_normalize({ env: this.env }, this.t) + ); + } + + type() { + return this.t.tag; + } + + pointSet(): result { + let pointSet = toPointSet( + this.t, + { + xyPointLength: this.env.xyPointLength, + sampleCount: this.env.sampleCount, + }, + undefined + ); + if (pointSet.tag === "Ok") { + let distribution = pointSet.value; + if (distribution.tag === "Continuous") { + return Ok({ + continuous: shapePoints(distribution.value), + discrete: [], + }); + } else if (distribution.tag === "Discrete") { + return Ok({ + discrete: shapePoints(distribution.value), + continuous: [], + }); + } else { + return Ok({ + discrete: shapePoints(distribution.value.discrete), + continuous: shapePoints(distribution.value.continuous), + }); + } + } else { + return pointSet; + } + } + + toPointSet(): result { + return this.mapResultDist( + Constructors_toPointSet({ env: this.env }, this.t) + ); + } + + toSampleSet(n: number): result { + return this.mapResultDist( + Constructors_toSampleSet({ env: this.env }, this.t, n) + ); + } + + truncate( + left: number, + right: number + ): result { + return this.mapResultDist( + Constructors_truncate({ env: this.env }, this.t, left, right) + ); + } + + inspect(): result { + return this.mapResultDist(Constructors_inspect({ env: this.env }, this.t)); + } + + toString(): string { + let result = Constructors_toString({ env: this.env }, this.t); + if (result.tag === "Ok") { + return result.value; + } else { + return distributionErrorToString(result.value); + } + } + + toSparkline(n: number): result { + return Constructors_toSparkline({ env: this.env }, this.t, n); + } + + algebraicAdd(d2: Distribution): result { + return this.mapResultDist( + Constructors_algebraicAdd({ env: this.env }, this.t, d2.t) + ); + } + + algebraicMultiply(d2: Distribution): result { + return this.mapResultDist( + Constructors_algebraicMultiply({ env: this.env }, this.t, d2.t) + ); + } + + algebraicDivide(d2: Distribution): result { + return this.mapResultDist( + Constructors_algebraicDivide({ env: this.env }, this.t, d2.t) + ); + } + + algebraicSubtract(d2: Distribution): result { + return this.mapResultDist( + Constructors_algebraicSubtract({ env: this.env }, this.t, d2.t) + ); + } + + algebraicLogarithm( + d2: Distribution + ): result { + return this.mapResultDist( + Constructors_algebraicLogarithm({ env: this.env }, this.t, d2.t) + ); + } + + algebraicPower(d2: Distribution): result { + return this.mapResultDist( + Constructors_algebraicPower({ env: this.env }, this.t, d2.t) + ); + } + + pointwiseAdd(d2: Distribution): result { + return this.mapResultDist( + Constructors_pointwiseAdd({ env: this.env }, this.t, d2.t) + ); + } + + pointwiseMultiply(d2: Distribution): result { + return this.mapResultDist( + Constructors_pointwiseMultiply({ env: this.env }, this.t, d2.t) + ); + } + + pointwiseDivide(d2: Distribution): result { + return this.mapResultDist( + Constructors_pointwiseDivide({ env: this.env }, this.t, d2.t) + ); + } + + pointwiseSubtract(d2: Distribution): result { + return this.mapResultDist( + Constructors_pointwiseSubtract({ env: this.env }, this.t, d2.t) + ); + } + + pointwiseLogarithm( + d2: Distribution + ): result { + return this.mapResultDist( + Constructors_pointwiseLogarithm({ env: this.env }, this.t, d2.t) + ); + } + + pointwisePower(d2: Distribution): result { + return this.mapResultDist( + Constructors_pointwisePower({ env: this.env }, this.t, d2.t) + ); + } +} diff --git a/packages/squiggle-lang/src/js/index.ts b/packages/squiggle-lang/src/js/index.ts index b944bc80..9b916d13 100644 --- a/packages/squiggle-lang/src/js/index.ts +++ b/packages/squiggle-lang/src/js/index.ts @@ -1,139 +1,92 @@ import * as _ from "lodash"; import { - genericDist, samplingParams, environment, + defaultEnvironment, evaluatePartialUsingExternalBindings, evaluateUsingOptions, externalBindings, expressionValue, - recordEV, errorValue, - distributionError, - toPointSet, - continuousShape, - discreteShape, - distributionErrorToString, - internalCode, - mixedShape, - sampleSetDist, - symbolicDist, - defaultEnvironment, - defaultSamplingEnv, } from "../rescript/TypescriptInterface.gen"; export { makeSampleSetDist, errorValueToString, distributionErrorToString, } from "../rescript/TypescriptInterface.gen"; +export type { + samplingParams, + errorValue, + externalBindings as bindings, + jsImports, +}; import { - Constructors_mean, - Constructors_sample, - Constructors_pdf, - Constructors_cdf, - Constructors_inv, - Constructors_normalize, - Constructors_isNormalized, - Constructors_toPointSet, - Constructors_toSampleSet, - Constructors_truncate, - Constructors_inspect, - Constructors_toString, - Constructors_toSparkline, - Constructors_algebraicAdd, - Constructors_algebraicMultiply, - Constructors_algebraicDivide, - Constructors_algebraicSubtract, - Constructors_algebraicLogarithm, - Constructors_algebraicPower, - Constructors_pointwiseAdd, - Constructors_pointwiseMultiply, - Constructors_pointwiseDivide, - Constructors_pointwiseSubtract, - Constructors_pointwiseLogarithm, - Constructors_pointwisePower, -} from "../rescript/Distributions/DistributionOperation/DistributionOperation.gen"; -export type { samplingParams, errorValue, externalBindings as bindings }; + jsValueToBinding, + jsValue, + rescriptExport, + squiggleExpression, + convertRawToTypescript, +} from "./rescript_interop"; +import { result, resultMap, tag, tagged } from "./types"; +import { Distribution } from "./distribution"; -export let defaultSamplingInputs: samplingParams = defaultSamplingEnv; - -export type result = - | { - tag: "Ok"; - value: a; - } - | { - tag: "Error"; - value: b; - }; - -export function resultMap( - r: result, - mapFn: (x: a) => b -): result { - if (r.tag === "Ok") { - return { tag: "Ok", value: mapFn(r.value) }; - } else { - return r; - } -} - -function Ok(x: a): result { - return { tag: "Ok", value: x }; -} - -type tagged = { tag: a; value: b }; - -function tag(x: a, y: b): tagged { - return { tag: x, value: y }; -} - -export type squiggleExpression = - | tagged<"symbol", string> - | tagged<"string", string> - | tagged<"call", string> - | tagged<"lambda", [string[], recordEV, internalCode]> - | tagged<"array", squiggleExpression[]> - | tagged<"arrayString", string[]> - | tagged<"boolean", boolean> - | tagged<"distribution", Distribution> - | tagged<"number", number> - | tagged<"record", { [key: string]: squiggleExpression }>; +export { Distribution, squiggleExpression, result, resultMap }; export function run( squiggleString: string, bindings?: externalBindings, - samplingInputs?: samplingParams, - environ?: environment + environment?: environment, + imports?: jsImports ): result { - let b = bindings ? bindings : {}; - let si: samplingParams = samplingInputs - ? samplingInputs - : defaultSamplingInputs; - let e = environ ? environ : defaultEnvironment; + let b = bindings ? bindings : defaultBindings; + let i = imports ? imports : defaultImports; + let e = environment ? environment : defaultEnvironment; let res: result = evaluateUsingOptions( - { externalBindings: b, environment: e }, + { externalBindings: mergeImports(b, i), environment: e }, squiggleString - ); // , b, e); - return resultMap(res, (x) => createTsExport(x, si)); + ); + return resultMap(res, (x) => createTsExport(x, e)); } // Run Partial. A partial is a block of code that doesn't return a value export function runPartial( squiggleString: string, - bindings: externalBindings, - _samplingInputs?: samplingParams + bindings?: externalBindings, + environment?: environment, + imports?: jsImports ): result { + let b = bindings ? bindings : defaultBindings; + let i = imports ? imports : defaultImports; + let e = environment ? environment : defaultEnvironment; + return evaluatePartialUsingExternalBindings( squiggleString, - bindings, - defaultEnvironment + mergeImports(b, i), + e ); } +function mergeImports( + bindings: externalBindings, + imports: jsImports +): externalBindings { + let transformedImports = Object.fromEntries( + Object.entries(imports).map(([key, value]) => [ + "$" + key, + jsValueToBinding(value), + ]) + ); + return _.merge(bindings, transformedImports); +} + +type jsImports = { [key: string]: jsValue }; + +export let defaultImports: jsImports = {}; +export let defaultBindings: externalBindings = {}; + function createTsExport( x: expressionValue, - sampEnv: samplingParams + environment: environment ): squiggleExpression { switch (x.tag) { case "EvArray": @@ -152,7 +105,10 @@ function createTsExport( return tag( "record", _.mapValues(arrayItem.value, (recordValue: unknown) => - convertRawToTypescript(recordValue as rescriptExport, sampEnv) + convertRawToTypescript( + recordValue as rescriptExport, + environment + ) ) ); case "EvArray": @@ -160,16 +116,16 @@ function createTsExport( return tag( "array", y.map((childArrayItem) => - convertRawToTypescript(childArrayItem, sampEnv) + convertRawToTypescript(childArrayItem, environment) ) ); default: - return createTsExport(arrayItem, sampEnv); + return createTsExport(arrayItem, environment); } }) ); case "EvArrayString": - return tag("arrayString", x.value); + return tag("arraystring", x.value); case "EvBool": return tag("boolean", x.value); case "EvCall": @@ -177,7 +133,7 @@ function createTsExport( case "EvLambda": return tag("lambda", x.value); case "EvDistribution": - return tag("distribution", new Distribution(x.value, sampEnv)); + return tag("distribution", new Distribution(x.value, environment)); case "EvNumber": return tag("number", x.value); case "EvRecord": @@ -185,7 +141,7 @@ function createTsExport( let result: tagged<"record", { [key: string]: squiggleExpression }> = tag( "record", _.mapValues(x.value, (x: unknown) => - convertRawToTypescript(x as rescriptExport, sampEnv) + convertRawToTypescript(x as rescriptExport, environment) ) ); return result; @@ -195,335 +151,3 @@ function createTsExport( return tag("symbol", x.value); } } - -// Helper functions to convert the rescript representations that genType doesn't -// cover -function convertRawToTypescript( - result: rescriptExport, - sampEnv: samplingParams -): squiggleExpression { - switch (result.TAG) { - case 0: // EvArray - return tag( - "array", - result._0.map((x) => convertRawToTypescript(x, sampEnv)) - ); - case 1: // EvBool - return tag("boolean", result._0); - case 2: // EvCall - return tag("call", result._0); - case 3: // EvDistribution - return tag( - "distribution", - new Distribution( - convertRawDistributionToGenericDist(result._0), - sampEnv - ) - ); - case 4: // EvNumber - return tag("number", result._0); - case 5: // EvRecord - return tag( - "record", - _.mapValues(result._0, (x) => convertRawToTypescript(x, sampEnv)) - ); - case 6: // EvString - return tag("string", result._0); - case 7: // EvSymbol - return tag("symbol", result._0); - case 8: // EvArrayString - return tag("arrayString", result._0); - } -} - -function convertRawDistributionToGenericDist( - result: rescriptDist -): genericDist { - switch (result.TAG) { - case 0: // Point Set Dist - switch (result._0.TAG) { - case 0: // Mixed - return tag("PointSet", tag("Mixed", result._0._0)); - case 1: // Discrete - return tag("PointSet", tag("Discrete", result._0._0)); - case 2: // Continuous - return tag("PointSet", tag("Continuous", result._0._0)); - } - case 1: // Sample Set Dist - return tag("SampleSet", result._0); - case 2: // Symbolic Dist - return tag("Symbolic", result._0); - } -} - -// Raw rescript types. -type rescriptExport = - | { - TAG: 0; // EvArray - _0: rescriptExport[]; - } - | { - TAG: 1; // EvBool - _0: boolean; - } - | { - TAG: 2; // EvCall - _0: string; - } - | { - TAG: 3; // EvDistribution - _0: rescriptDist; - } - | { - TAG: 4; // EvNumber - _0: number; - } - | { - TAG: 5; // EvRecord - _0: { [key: string]: rescriptExport }; - } - | { - TAG: 6; // EvString - _0: string; - } - | { - TAG: 7; // EvSymbol - _0: string; - } - | { - TAG: 8; // EvArrayString - _0: string[]; - }; - -type rescriptDist = - | { TAG: 0; _0: rescriptPointSetDist } - | { TAG: 1; _0: sampleSetDist } - | { TAG: 2; _0: symbolicDist }; - -type rescriptPointSetDist = - | { - TAG: 0; // Mixed - _0: mixedShape; - } - | { - TAG: 1; // Discrete - _0: discreteShape; - } - | { - TAG: 2; // ContinuousShape - _0: continuousShape; - }; - -export function resultExn(r: result): a | c { - return r.value; -} - -export type point = { x: number; y: number }; - -export type shape = { - continuous: point[]; - discrete: point[]; -}; - -function shapePoints(x: continuousShape | discreteShape): point[] { - let xs = x.xyShape.xs; - let ys = x.xyShape.ys; - return _.zipWith(xs, ys, (x, y) => ({ x, y })); -} - -export class Distribution { - t: genericDist; - env: samplingParams; - - constructor(t: genericDist, env: samplingParams) { - this.t = t; - this.env = env; - return this; - } - - mapResultDist( - r: result - ): result { - return resultMap(r, (v: genericDist) => new Distribution(v, this.env)); - } - - mean(): result { - return Constructors_mean({ env: this.env }, this.t); - } - - sample(): result { - return Constructors_sample({ env: this.env }, this.t); - } - - pdf(n: number): result { - return Constructors_pdf({ env: this.env }, this.t, n); - } - - cdf(n: number): result { - return Constructors_cdf({ env: this.env }, this.t, n); - } - - inv(n: number): result { - return Constructors_inv({ env: this.env }, this.t, n); - } - - isNormalized(): result { - return Constructors_isNormalized({ env: this.env }, this.t); - } - - normalize(): result { - return this.mapResultDist( - Constructors_normalize({ env: this.env }, this.t) - ); - } - - type() { - return this.t.tag; - } - - pointSet(): result { - let pointSet = toPointSet( - this.t, - { - xyPointLength: this.env.xyPointLength, - sampleCount: this.env.sampleCount, - }, - undefined - ); - if (pointSet.tag === "Ok") { - let distribution = pointSet.value; - if (distribution.tag === "Continuous") { - return Ok({ - continuous: shapePoints(distribution.value), - discrete: [], - }); - } else if (distribution.tag === "Discrete") { - return Ok({ - discrete: shapePoints(distribution.value), - continuous: [], - }); - } else { - return Ok({ - discrete: shapePoints(distribution.value.discrete), - continuous: shapePoints(distribution.value.continuous), - }); - } - } else { - return pointSet; - } - } - - toPointSet(): result { - return this.mapResultDist( - Constructors_toPointSet({ env: this.env }, this.t) - ); - } - - toSampleSet(n: number): result { - return this.mapResultDist( - Constructors_toSampleSet({ env: this.env }, this.t, n) - ); - } - - truncate( - left: number, - right: number - ): result { - return this.mapResultDist( - Constructors_truncate({ env: this.env }, this.t, left, right) - ); - } - - inspect(): result { - return this.mapResultDist(Constructors_inspect({ env: this.env }, this.t)); - } - - toString(): string { - let result = Constructors_toString({ env: this.env }, this.t); - if (result.tag === "Ok") { - return result.value; - } else { - return distributionErrorToString(result.value); - } - } - - toSparkline(n: number): result { - return Constructors_toSparkline({ env: this.env }, this.t, n); - } - - algebraicAdd(d2: Distribution): result { - return this.mapResultDist( - Constructors_algebraicAdd({ env: this.env }, this.t, d2.t) - ); - } - - algebraicMultiply(d2: Distribution): result { - return this.mapResultDist( - Constructors_algebraicMultiply({ env: this.env }, this.t, d2.t) - ); - } - - algebraicDivide(d2: Distribution): result { - return this.mapResultDist( - Constructors_algebraicDivide({ env: this.env }, this.t, d2.t) - ); - } - - algebraicSubtract(d2: Distribution): result { - return this.mapResultDist( - Constructors_algebraicSubtract({ env: this.env }, this.t, d2.t) - ); - } - - algebraicLogarithm( - d2: Distribution - ): result { - return this.mapResultDist( - Constructors_algebraicLogarithm({ env: this.env }, this.t, d2.t) - ); - } - - algebraicPower(d2: Distribution): result { - return this.mapResultDist( - Constructors_algebraicPower({ env: this.env }, this.t, d2.t) - ); - } - - pointwiseAdd(d2: Distribution): result { - return this.mapResultDist( - Constructors_pointwiseAdd({ env: this.env }, this.t, d2.t) - ); - } - - pointwiseMultiply(d2: Distribution): result { - return this.mapResultDist( - Constructors_pointwiseMultiply({ env: this.env }, this.t, d2.t) - ); - } - - pointwiseDivide(d2: Distribution): result { - return this.mapResultDist( - Constructors_pointwiseDivide({ env: this.env }, this.t, d2.t) - ); - } - - pointwiseSubtract(d2: Distribution): result { - return this.mapResultDist( - Constructors_pointwiseSubtract({ env: this.env }, this.t, d2.t) - ); - } - - pointwiseLogarithm( - d2: Distribution - ): result { - return this.mapResultDist( - Constructors_pointwiseLogarithm({ env: this.env }, this.t, d2.t) - ); - } - - pointwisePower(d2: Distribution): result { - return this.mapResultDist( - Constructors_pointwisePower({ env: this.env }, this.t, d2.t) - ); - } -} diff --git a/packages/squiggle-lang/src/js/rescript_interop.ts b/packages/squiggle-lang/src/js/rescript_interop.ts new file mode 100644 index 00000000..75c6e733 --- /dev/null +++ b/packages/squiggle-lang/src/js/rescript_interop.ts @@ -0,0 +1,171 @@ +import * as _ from "lodash"; +import { + mixedShape, + sampleSetDist, + genericDist, + environment, + symbolicDist, + recordEV, + internalCode, + discreteShape, + continuousShape, +} from "../rescript/TypescriptInterface.gen"; +import { Distribution } from "./distribution"; +import { tagged, tag } from "./types"; +// This file is here to compensate for genType not fully recursively converting types + +// Raw rescript types. +export type rescriptExport = + | { + TAG: 0; // EvArray + _0: rescriptExport[]; + } + | { + TAG: 1; // EvString + _0: string[]; + } + | { + TAG: 2; // EvBool + _0: boolean; + } + | { + TAG: 3; // EvCall + _0: string; + } + | { + TAG: 4; // EvDistribution + _0: rescriptDist; + } + | { + TAG: 5; // EvLambda + _0: [string[], recordEV, internalCode]; + } + | { + TAG: 6; // EvNumber + _0: number; + } + | { + TAG: 7; // EvRecord + _0: { [key: string]: rescriptExport }; + } + | { + TAG: 8; // EvString + _0: string; + } + | { + TAG: 9; // EvSymbol + _0: string; + }; + +type rescriptDist = + | { TAG: 0; _0: rescriptPointSetDist } + | { TAG: 1; _0: sampleSetDist } + | { TAG: 2; _0: symbolicDist }; + +type rescriptPointSetDist = + | { + TAG: 0; // Mixed + _0: mixedShape; + } + | { + TAG: 1; // Discrete + _0: discreteShape; + } + | { + TAG: 2; // ContinuousShape + _0: continuousShape; + }; + +export type squiggleExpression = + | tagged<"symbol", string> + | tagged<"string", string> + | tagged<"call", string> + | tagged<"lambda", [string[], recordEV, internalCode]> + | tagged<"array", squiggleExpression[]> + | tagged<"arraystring", string[]> + | tagged<"boolean", boolean> + | tagged<"distribution", Distribution> + | tagged<"number", number> + | tagged<"record", { [key: string]: squiggleExpression }>; + +export function convertRawToTypescript( + result: rescriptExport, + environment: environment +): squiggleExpression { + switch (result.TAG) { + case 0: // EvArray + return tag( + "array", + result._0.map((x) => convertRawToTypescript(x, environment)) + ); + case 1: // EvArrayString + return tag("arraystring", result._0); + case 2: // EvBool + return tag("boolean", result._0); + case 3: // EvCall + return tag("call", result._0); + case 4: // EvDistribution + return tag( + "distribution", + new Distribution( + convertRawDistributionToGenericDist(result._0), + environment + ) + ); + case 5: // EvDistribution + return tag("lambda", result._0); + case 6: // EvNumber + return tag("number", result._0); + case 7: // EvRecord + return tag( + "record", + _.mapValues(result._0, (x) => convertRawToTypescript(x, environment)) + ); + case 8: // EvString + return tag("string", result._0); + case 9: // EvSymbol + return tag("symbol", result._0); + } +} + +function convertRawDistributionToGenericDist( + result: rescriptDist +): genericDist { + switch (result.TAG) { + case 0: // Point Set Dist + switch (result._0.TAG) { + case 0: // Mixed + return tag("PointSet", tag("Mixed", result._0._0)); + case 1: // Discrete + return tag("PointSet", tag("Discrete", result._0._0)); + case 2: // Continuous + return tag("PointSet", tag("Continuous", result._0._0)); + } + case 1: // Sample Set Dist + return tag("SampleSet", result._0); + case 2: // Symbolic Dist + return tag("Symbolic", result._0); + } +} + +export type jsValue = + | string + | number + | jsValue[] + | { [key: string]: jsValue } + | boolean; + +export function jsValueToBinding(value: jsValue): rescriptExport { + if (typeof value === "boolean") { + return { TAG: 2, _0: value as boolean }; + } else if (typeof value === "string") { + return { TAG: 8, _0: value as string }; + } else if (typeof value === "number") { + return { TAG: 6, _0: value as number }; + } else if (Array.isArray(value)) { + return { TAG: 0, _0: value.map(jsValueToBinding) }; + } else { + // Record + return { TAG: 7, _0: _.mapValues(value, jsValueToBinding) }; + } +} diff --git a/packages/squiggle-lang/src/js/types.ts b/packages/squiggle-lang/src/js/types.ts new file mode 100644 index 00000000..8851b520 --- /dev/null +++ b/packages/squiggle-lang/src/js/types.ts @@ -0,0 +1,30 @@ +export type result = + | { + tag: "Ok"; + value: a; + } + | { + tag: "Error"; + value: b; + }; + +export function resultMap( + r: result, + mapFn: (x: a) => b +): result { + if (r.tag === "Ok") { + return { tag: "Ok", value: mapFn(r.value) }; + } else { + return r; + } +} + +export function Ok(x: a): result { + return { tag: "Ok", value: x }; +} + +export type tagged = { tag: a; value: b }; + +export function tag(x: a, y: b): tagged { + return { tag: x, value: y }; +} diff --git a/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.res b/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.res index 18ee2d6a..29976711 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.res +++ b/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.res @@ -9,6 +9,11 @@ type env = { xyPointLength: int, } +let defaultEnv = { + sampleCount: 10000, + xyPointLength: 10000, +} + type outputType = | Dist(genericDist) | Float(float) diff --git a/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.resi b/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.resi index 5ad34354..d7f4f84e 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.resi +++ b/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.resi @@ -4,6 +4,9 @@ type env = { xyPointLength: int, } +@genType +let defaultEnv: env + open DistributionTypes @genType diff --git a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res index 31168425..60300c82 100644 --- a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res +++ b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res @@ -91,6 +91,7 @@ let toStringResultRecord = x => } @genType -type environment = {dummy: int} +type environment = DistributionOperation.env + @genType -let defaultEnvironment: environment = {dummy: 0} +let defaultEnvironment: environment = DistributionOperation.defaultEnv diff --git a/packages/squiggle-lang/src/rescript/Utility/E.res b/packages/squiggle-lang/src/rescript/Utility/E.res index e0bcaf5c..f5b38be7 100644 --- a/packages/squiggle-lang/src/rescript/Utility/E.res +++ b/packages/squiggle-lang/src/rescript/Utility/E.res @@ -350,7 +350,7 @@ module JsDate = { /* List */ module L = { module Util = { - let eq = (a, b) => a == b + let eq = \"==" } let fmap = List.map let get = Belt.List.get