Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simple Squiggle Calculators #2265

Merged
merged 23 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
169 changes: 169 additions & 0 deletions packages/components/src/components/Calculator/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import React, { FC, ReactNode, useState, useEffect } from "react";

import {
SqValue,
SqCalculator,
SqError,
SqProject,
result,
} from "@quri/squiggle-lang";
import { Env } from "@quri/squiggle-lang";

import { PlaygroundSettings } from "../PlaygroundSettings.js";
import { SqValueWithContext, valueHasContext } from "../../lib/utility.js";

type Props = {
value: SqCalculator;
environment: Env;
settings: PlaygroundSettings;
renderValue: (
value: SqValueWithContext,
settings: PlaygroundSettings
) => ReactNode;
};
//calc.run([output.value.result], environment);
const runSquiggleCode = async (
calc: SqCalculator,
code: string,
environment: Env
): Promise<result<SqValue, SqError>> => {
const project = SqProject.create();
if (environment) {
project.setEnvironment(environment);
}
const sourceId = "test";
project.setSource(sourceId, code);
await project.run(sourceId);
const output = project.getOutput(sourceId);
if (output.ok) {
const result: result<SqValue, SqError> = {
ok: true,
value: output.value.result,
};
return result;
} else {
return output;
}
};

export const Calculator: FC<Props> = ({
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this file is messier than it could be. That said, I think cleaning it up could be a fair bit of work. Maybe a good fit for a few hooks or something.

value,
environment,
settings,
renderValue,
}) => {
const [codes, setCodes] = useState<Record<string, string>>(() => {
const initialCodes: Record<string, string> = {};
value.names.forEach((name) => {
initialCodes[name] = "40"; // Initial code value. This can be changed as needed.
});
return initialCodes;
});

const [cachedResults, setCachedResults] = useState<
Record<string, result<SqValue, SqError> | null>
>({});

const [finalResult, setFinalResult] = useState<result<
SqValue,
SqError
> | null>();

useEffect(() => {
const fetchResults = async () => {
const newResults: Record<string, result<SqValue, SqError> | null> = {};

// Fetch all results
for (const name of value.names) {
const code = codes[name];
const res = await runSquiggleCode(value, code, environment);
newResults[name] = res;
}

// Once all results are fetched, update the state
setCachedResults(newResults);

// Check validity
const allCodesAreValid = value.names.every((name) => {
const result = newResults[name];
return result && result.ok;
});

// If all codes are valid, calculate the final result
if (allCodesAreValid) {
const results: SqValue[] = value.names.map((name) => {
const res = newResults[name];
if (res && res.ok) {
return res.value;
} else {
// This shouldn't happen since we've already checked if all codes are valid.
// Just a fallback.
throw new Error("Invalid result encountered.");
}
});
const finalResult: result<SqValue, SqError> = value.run(
results,
environment
);
setFinalResult(finalResult);
}
};

fetchResults();
}, [value, codes, environment]);

const handleChange =
(name: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
const newCode = e.target.value;
setCodes((prevCodes) => ({
...prevCodes,
[name]: newCode,
}));
};

const showItem = (
item: result<SqValue, SqError>,
settings: PlaygroundSettings
) => {
if (item.ok) {
const value = item.value;
if (valueHasContext(value)) {
return renderValue(value, settings);
} else {
return value.toString();
}
} else {
return item.value.toString();
}
};

const chartHeight = 50;
const distributionChartSettings = {
...settings.distributionChartSettings,
showSummary: false,
};
const adjustedSettings: PlaygroundSettings = {
...settings,
distributionChartSettings,
chartHeight,
};

return (
<div className="relative rounded-sm overflow-hidden border border-slate-200">
{value.names.map((name) => (
<div key={name}>
<input
value={codes[name] || ""}
onChange={handleChange(name)}
placeholder={`Enter code for ${name}`}
className="my-2 p-2 border rounded"
/>
{cachedResults[name]
? showItem(cachedResults[name]!, adjustedSettings)

Check warning on line 162 in packages/components/src/components/Calculator/index.tsx

View workflow job for this annotation

GitHub Actions / Build, test, lint

Forbidden non-null assertion
: "Loading..."}
</div>
))}
{finalResult && finalResult.ok && showItem(finalResult, adjustedSettings)}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { ReactNode } from "react";

import {
SqCalculator,
SqDistributionsPlot,
SqPlot,
SqScale,
Expand Down Expand Up @@ -31,6 +32,7 @@ import { TableChart } from "../TableChart/index.js";
import { DistPreview } from "../DistributionsChart/DistPreview.js";
import { TableCellsIcon } from "@quri/ui";
import ReactMarkdown from "react-markdown";
import { Calculator } from "../Calculator/index.js";

// We use an extra left margin for some elements to align them with parent variable name
const leftMargin = "ml-1.5";
Expand Down Expand Up @@ -137,6 +139,21 @@ export const getBoxProps = (
children: () => <NumberShower precision={3} number={value.value} />,
};
}
case "Calculator": {
const calculator: SqCalculator = value.value;
return {
children: (settings) => (
<Calculator
value={calculator}
environment={environment}
settings={settings}
renderValue={(value, settings) =>
getBoxProps(value).children(settings)
}
/>
),
};
}
case "TableChart": {
const table: SqTableChart = value.value;
return {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Meta, StoryObj } from "@storybook/react";

import { SquiggleChart } from "../../components/SquiggleChart.js";

const meta = {
component: SquiggleChart,
} satisfies Meta<typeof SquiggleChart>;
export default meta;
type Story = StoryObj<typeof meta>;

export const Basic: Story = {
args: {
code: `
Calculator.make(
{
fn: {|a,b,c|a+b+c},
rows: ["a", "b", "c"]
}
)
OAGr marked this conversation as resolved.
Show resolved Hide resolved
`,
},
};
33 changes: 33 additions & 0 deletions packages/squiggle-lang/src/fr/calculator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { makeDefinition } from "../library/registry/fnDefinition.js";
import {
frDict,
frLambda,
frArray,
frString,
} from "../library/registry/frTypes.js";
import { FnFactory } from "../library/registry/helpers.js";
import { vCalculator } from "../value/index.js";

const maker = new FnFactory({
nameSpace: "Calculator",
requiresNamespace: true,
});

export const library = [
maker.make({
name: "make",
output: "Calculator",
examples: [],
definitions: [
makeDefinition(
[frDict(["fn", frLambda], ["rows", frArray(frString)])],
([{ fn, rows }]) => {
return vCalculator({
fn,
inputs: rows.map((name) => ({ name })),
});
}
),
],
}),
];
1 change: 1 addition & 0 deletions packages/squiggle-lang/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export {
type SqPlot,
} from "./public/SqValue/SqPlot.js";
export { SqTableChart } from "./public/SqValue/SqTableChart.js";
export { SqCalculator } from "./public/SqValue/SqCalculator.js";
export { SqDict } from "./public/SqValue/SqDict.js";
export {
SqLinearScale,
Expand Down
2 changes: 2 additions & 0 deletions packages/squiggle-lang/src/library/registry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { library as dateLibrary } from "../../fr/date.js";
import { library as dictLibrary } from "../../fr/dict.js";
import { library as distLibrary } from "../../fr/dist.js";
import { library as genericDistLibrary } from "../../fr/genericDist.js";
import { library as calculatorLibrary } from "../../fr/calculator.js";
import { library as listLibrary } from "../../fr/list.js";
import { library as mathLibrary } from "../../fr/math.js";
import { library as numberLibrary } from "../../fr/number.js";
Expand Down Expand Up @@ -46,6 +47,7 @@ const fnList: FRFunction[] = [
...scoringLibrary,
...symLibrary,
...unitsLibrary,
...calculatorLibrary,
];

export const registry = Registry.make(fnList);
Expand Down
36 changes: 36 additions & 0 deletions packages/squiggle-lang/src/public/SqValue/SqCalculator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Calculator } from "../../value/index.js";
import { Env } from "../../dist/env.js";
import * as Result from "../../utility/result.js";

import { SqError, SqOtherError } from "../SqError.js";
import { SqValueContext } from "../SqValueContext.js";
import { SqLambda } from "./SqLambda.js";
import { SqValue, wrapValue } from "./index.js";
import { Lambda } from "../../reducer/lambda.js";

const wrapFn = ({ fn }: { fn: Lambda }): SqLambda => {
return new SqLambda(fn, undefined);
};
OAGr marked this conversation as resolved.
Show resolved Hide resolved

export class SqCalculator {
constructor(private _value: Calculator, public context?: SqValueContext) {}

run(items: SqValue[], env: Env): Result.result<SqValue, SqError> {
const response = wrapFn({ fn: this._value.fn }).call(items, env);
const context = this.context;

const newContext: SqValueContext | undefined = context;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like context.extend({ type: 'call' }), with a new PathItem subtype, serializable to ()?

This should be enough; we don't want to add argument values there, since we want to keep local settings for calculator outputs when arguments change.

You'll also have to patch SqValueContext.extend implementation, because right now it checks for itemisNotTableIndex; it should exclude call types too.


if (response.ok && context) {
return Result.Ok(wrapValue(response.value._value, newContext));
} else if (response.ok) {
return Result.Err(new SqOtherError("Context creation for table failed."));
OAGr marked this conversation as resolved.
Show resolved Hide resolved
} else {
return response;
}
}

get names(): string[] {
return this._value.inputs.map((x) => x.name || "");
}
}
17 changes: 17 additions & 0 deletions packages/squiggle-lang/src/public/SqValue/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Value, vLambda, vNumber, vString } from "../../value/index.js";
import { SqError } from "../SqError.js";
import { SqValueContext } from "../SqValueContext.js";
import { SqArray } from "./SqArray.js";
import { SqCalculator } from "./SqCalculator.js";
import { SqDict } from "./SqDict.js";
import { SqDistribution, wrapDistribution } from "./SqDistribution/index.js";
import { SqDomain, wrapDomain } from "./SqDomain.js";
Expand Down Expand Up @@ -33,6 +34,8 @@ export function wrapValue(value: Value, context?: SqValueContext) {
return new SqPlotValue(value, context);
case "TableChart":
return new SqTableChartValue(value, context);
case "Calculator":
return new SqCalculatorValue(value, context);
case "Scale":
return new SqScaleValue(value, context);
case "TimeDuration":
Expand Down Expand Up @@ -212,6 +215,20 @@ export class SqTableChartValue extends SqAbstractValue<
return this.value;
}
}
export class SqCalculatorValue extends SqAbstractValue<
"Calculator",
SqCalculator
> {
tag = "Calculator" as const;

get value() {
return new SqCalculator(this._value.value, this.context);
}

asJS() {
return this.value;
}
}

export class SqScaleValue extends SqAbstractValue<"Scale", SqScale> {
tag = "Scale" as const;
Expand Down
19 changes: 19 additions & 0 deletions packages/squiggle-lang/src/value/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,24 @@ class VTableChart extends BaseValue {

export const vTableChart = (v: TableChart) => new VTableChart(v);

export type Calculator = {
inputs: { name: string | undefined }[];
fn: Lambda;
};

class VCalculator extends BaseValue {
readonly type = "Calculator";

constructor(public value: Calculator) {
super();
}
toString() {
return `Calculator`;
}
}

export const vCalculator = (v: Calculator) => new VCalculator(v);

class VPlot extends BaseValue implements Indexable {
readonly type = "Plot";

Expand Down Expand Up @@ -413,6 +431,7 @@ export type Value =
| VTimeDuration
| VPlot
| VTableChart
| VCalculator
| VScale
| VDomain
| VVoid;