Merge remote-tracking branch 'origin/develop' into scoring-cleanup-three

This commit is contained in:
Quinn Dougherty 2022-07-11 14:20:00 -04:00
commit d682f28faf
56 changed files with 2659 additions and 1754 deletions

4
.github/CODEOWNERS vendored
View File

@ -9,8 +9,8 @@
# This also holds true for GitHub teams.
# Rescript
*.res @OAGr @quinn-dougherty
*.resi @OAGr @quinn-dougherty
*.res @OAGr
*.resi @OAGr
# Typescript
*.tsx @Hazelfire @OAGr

View File

@ -20,7 +20,7 @@ Add to `App.js`:
```jsx
import { SquiggleEditor } from "@quri/squiggle-components";
<SquiggleEditor
initialSquiggleString="x = beta($alpha, 10); x + $shift"
defaultCode="x = beta($alpha, 10); x + $shift"
jsImports={{ alpha: 3, shift: 20 }}
/>;
```
@ -50,7 +50,7 @@ export function DynamicSquiggleChart({ squiggleString }) {
} else {
return (
<SquiggleChart
squiggleString={squiggleString}
defaultCode={squiggleString}
width={445}
height={200}
showSummary={true}

View File

@ -3,26 +3,27 @@
"version": "0.2.20",
"license": "MIT",
"dependencies": {
"@headlessui/react": "^1.6.5",
"@headlessui/react": "^1.6.6",
"@heroicons/react": "^1.0.6",
"@hookform/resolvers": "^2.9.3",
"@hookform/resolvers": "^2.9.5",
"@quri/squiggle-lang": "^0.2.8",
"@react-hook/size": "^2.1.2",
"clsx": "^1.1.1",
"clsx": "^1.2.1",
"framer-motion": "^6.4.3",
"lodash": "^4.17.21",
"react": "^18.1.0",
"react-ace": "^10.1.0",
"react-hook-form": "^7.33.0",
"react-hook-form": "^7.33.1",
"react-use": "^17.4.0",
"react-vega": "^7.5.1",
"react-vega": "^7.6.0",
"vega": "^5.22.1",
"vega-embed": "^6.21.0",
"vega-lite": "^5.2.0",
"vega-lite": "^5.3.0",
"vscode-uri": "^3.0.3",
"yup": "^0.32.11"
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.17.12",
"@babel/plugin-proposal-private-property-in-object": "^7.18.6",
"@storybook/addon-actions": "^6.5.9",
"@storybook/addon-essentials": "^6.5.9",
"@storybook/addon-links": "^6.5.9",
@ -36,28 +37,26 @@
"@testing-library/user-event": "^14.2.1",
"@types/jest": "^27.5.0",
"@types/lodash": "^4.14.182",
"@types/node": "^18.0.0",
"@types/node": "^18.0.3",
"@types/react": "^18.0.9",
"@types/react-dom": "^18.0.5",
"@types/styled-components": "^5.1.24",
"@types/webpack": "^5.28.0",
"cross-env": "^7.0.3",
"mini-css-extract-plugin": "^2.6.1",
"postcss-cli": "^9.1.0",
"postcss-cli": "^10.0.0",
"postcss-import": "^14.1.0",
"postcss-loader": "^7.0.0",
"postcss-loader": "^7.0.1",
"react": "^18.1.0",
"react-dom": "^18.2.0",
"react-scripts": "^5.0.1",
"style-loader": "^3.3.1",
"tailwindcss": "^3.1.3",
"tailwindcss": "^3.1.5",
"ts-loader": "^9.3.0",
"tsconfig-paths-webpack-plugin": "^3.5.2",
"typescript": "^4.7.4",
"web-vitals": "^2.1.4",
"webpack": "^5.73.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.9.2"
"webpack-dev-server": "^4.9.3"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18",

View File

@ -1,5 +1,5 @@
import _ from "lodash";
import React, { FC, useMemo } from "react";
import React, { FC, useMemo, useRef } from "react";
import AceEditor from "react-ace";
import "ace-builds/src-noconflict/mode-golang";
@ -8,6 +8,7 @@ import "ace-builds/src-noconflict/theme-github";
interface CodeEditorProps {
value: string;
onChange: (value: string) => void;
onSubmit?: () => void;
oneLine?: boolean;
width?: number;
height: number;
@ -17,6 +18,7 @@ interface CodeEditorProps {
export const CodeEditor: FC<CodeEditorProps> = ({
value,
onChange,
onSubmit,
oneLine = false,
showGutter = false,
height,
@ -24,6 +26,10 @@ export const CodeEditor: FC<CodeEditorProps> = ({
const lineCount = value.split("\n").length;
const id = useMemo(() => _.uniqueId(), []);
// this is necessary because AceEditor binds commands on mount, see https://github.com/securingsincity/react-ace/issues/684
const onSubmitRef = useRef<typeof onSubmit | null>(null);
onSubmitRef.current = onSubmit;
return (
<AceEditor
value={value}
@ -46,6 +52,13 @@ export const CodeEditor: FC<CodeEditorProps> = ({
enableBasicAutocompletion: false,
enableLiveAutocompletion: false,
}}
commands={[
{
name: "submit",
bindKey: { mac: "Cmd-Enter", win: "Ctrl-Enter" },
exec: () => onSubmitRef.current?.(),
},
]}
/>
);
};

View File

@ -8,20 +8,23 @@ import {
defaultBindings,
defaultEnvironment,
} from "@quri/squiggle-lang";
import { FunctionChartSettings } from "./FunctionChart";
import { useSquiggle } from "../lib/hooks";
import { SquiggleErrorAlert } from "./SquiggleErrorAlert";
import { SquiggleItem } from "./SquiggleItem";
export interface SquiggleChartProps {
/** The input string for squiggle */
squiggleString?: string;
code?: string;
/** If the output requires monte carlo sampling, the amount of samples */
sampleCount?: number;
/** The amount of points returned to draw the distribution */
environment?: environment;
/** If the result is a function, where the function starts, ends and the amount of stops */
chartSettings?: FunctionChartSettings;
/** If the result is a function, where the function domain starts */
diagramStart?: number;
/** If the result is a function, where the function domain ends */
diagramStop?: number;
/** If the result is a function, the amount of stops sampled */
diagramCount?: number;
/** When the squiggle code gets reevaluated */
onChange?(expr: squiggleExpression | undefined): void;
/** CSS width of the element */
@ -44,51 +47,60 @@ export interface SquiggleChartProps {
}
const defaultOnChange = () => {};
const defaultChartSettings = { start: 0, stop: 10, count: 20 };
export const SquiggleChart: React.FC<SquiggleChartProps> = ({
squiggleString = "",
environment,
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
height = 200,
bindings = defaultBindings,
jsImports = defaultImports,
showSummary = false,
width,
showTypes = false,
showControls = false,
logX = false,
expY = false,
chartSettings = defaultChartSettings,
}) => {
const { result } = useSquiggle({
code: squiggleString,
bindings,
export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
({
code = "",
environment,
jsImports,
onChange,
});
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
height = 200,
bindings = defaultBindings,
jsImports = defaultImports,
showSummary = false,
width,
showTypes = false,
showControls = false,
logX = false,
expY = false,
diagramStart = 0,
diagramStop = 10,
diagramCount = 100,
}) => {
const result = useSquiggle({
code,
bindings,
environment,
jsImports,
onChange,
});
if (result.tag !== "Ok") {
return <SquiggleErrorAlert error={result.value} />;
if (result.tag !== "Ok") {
return <SquiggleErrorAlert error={result.value} />;
}
let distributionPlotSettings = {
showControls,
showSummary,
logX,
expY,
};
let chartSettings = {
start: diagramStart,
stop: diagramStop,
count: diagramCount,
};
return (
<SquiggleItem
expression={result.value}
width={width}
height={height}
distributionPlotSettings={distributionPlotSettings}
showTypes={showTypes}
chartSettings={chartSettings}
environment={environment ?? defaultEnvironment}
/>
);
}
let distributionPlotSettings = {
showControls,
showSummary,
logX,
expY,
};
return (
<SquiggleItem
expression={result.value}
width={width}
height={height}
distributionPlotSettings={distributionPlotSettings}
showTypes={showTypes}
chartSettings={chartSettings}
environment={environment ?? defaultEnvironment}
/>
);
};
);

View File

@ -1,18 +1,11 @@
import React, { useState } from "react";
import * as ReactDOM from "react-dom";
import React from "react";
import { CodeEditor } from "./CodeEditor";
import {
squiggleExpression,
environment,
bindings,
jsImports,
defaultEnvironment,
} from "@quri/squiggle-lang";
import { environment, bindings, jsImports } from "@quri/squiggle-lang";
import { defaultImports, defaultBindings } from "@quri/squiggle-lang";
import { SquiggleContainer } from "./SquiggleContainer";
import { useSquiggle, useSquigglePartial } from "../lib/hooks";
import { SquiggleChart, SquiggleChartProps } from "./SquiggleChart";
import { useSquigglePartial, useMaybeControlledValue } from "../lib/hooks";
import { SquiggleErrorAlert } from "./SquiggleErrorAlert";
import { SquiggleItem } from "./SquiggleItem";
const WrappedCodeEditor: React.FC<{
code: string;
@ -29,113 +22,36 @@ const WrappedCodeEditor: React.FC<{
</div>
);
export interface SquiggleEditorProps {
/** The input string for squiggle */
initialSquiggleString?: string;
/** The width of the element */
width?: number;
/** If the result is a function, where the function starts */
diagramStart?: number;
/** If the result is a function, where the function ends */
diagramStop?: number;
/** If the result is a function, how many points along the function it samples */
diagramCount?: number;
/** When the environment changes. Used again for notebook magic */
onChange?(expr: squiggleExpression | undefined): void;
/** Previous variable declarations */
bindings?: bindings;
/** If the output requires monte carlo sampling, the amount of samples */
environment?: environment;
/** JS Imports */
jsImports?: jsImports;
/** Whether to show detail about types of the returns, default false */
showTypes?: boolean;
/** Whether to give users access to graph controls */
showControls?: boolean;
/** Whether to show a summary table */
showSummary?: boolean;
/** Whether to log the x coordinate on distribution charts */
logX?: boolean;
/** Whether to exp the y coordinate on distribution charts */
expY?: boolean;
}
export type SquiggleEditorProps = SquiggleChartProps & {
defaultCode?: string;
onCodeChange?: (code: string) => void;
};
export const SquiggleEditor: React.FC<SquiggleEditorProps> = ({
initialSquiggleString = "",
width,
diagramStart = 0,
diagramStop = 10,
diagramCount = 20,
onChange,
bindings = defaultBindings,
environment,
jsImports = defaultImports,
showTypes = false,
showControls = false,
showSummary = false,
logX = false,
expY = false,
}: SquiggleEditorProps) => {
const [code, setCode] = useState(initialSquiggleString);
React.useEffect(
() => setCode(initialSquiggleString),
[initialSquiggleString]
);
const { result, observableRef } = useSquiggle({
code,
bindings,
environment,
jsImports,
onChange,
export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => {
const [code, setCode] = useMaybeControlledValue({
value: props.code,
defaultValue: props.defaultCode ?? "",
onChange: props.onCodeChange,
});
const chartSettings = {
start: diagramStart,
stop: diagramStop,
count: diagramCount,
};
const distributionPlotSettings = {
showControls,
showSummary,
logX,
expY,
};
let chartProps = { ...props, code };
return (
<div ref={observableRef}>
<SquiggleContainer>
<WrappedCodeEditor code={code} setCode={setCode} />
{result.tag === "Ok" ? (
<SquiggleItem
expression={result.value}
width={width}
height={200}
distributionPlotSettings={distributionPlotSettings}
showTypes={showTypes}
chartSettings={chartSettings}
environment={environment ?? defaultEnvironment}
/>
) : (
<SquiggleErrorAlert error={result.value} />
)}
</SquiggleContainer>
</div>
<SquiggleContainer>
<WrappedCodeEditor code={code} setCode={setCode} />
<SquiggleChart {...chartProps} />
</SquiggleContainer>
);
};
export function renderSquiggleEditorToDom(props: SquiggleEditorProps) {
const parent = document.createElement("div");
ReactDOM.render(<SquiggleEditor {...props} />, parent);
return parent;
}
export interface SquigglePartialProps {
/** The input string for squiggle */
initialSquiggleString?: string;
/** The text inside the input (controlled) */
code?: string;
/** The default text inside the input (unControlled) */
defaultCode?: string;
/** when the environment changes. Used again for notebook magic*/
onChange?(expr: bindings | undefined): void;
/** When the code changes */
onCodeChange?(code: string): void;
/** Previously declared variables */
bindings?: bindings;
/** If the output requires monte carlo sampling, the amount of samples */
@ -145,19 +61,21 @@ export interface SquigglePartialProps {
}
export const SquigglePartial: React.FC<SquigglePartialProps> = ({
initialSquiggleString = "",
code: controlledCode,
defaultCode = "",
onChange,
onCodeChange,
bindings = defaultBindings,
environment,
jsImports = defaultImports,
}: SquigglePartialProps) => {
const [code, setCode] = useState(initialSquiggleString);
React.useEffect(
() => setCode(initialSquiggleString),
[initialSquiggleString]
);
const [code, setCode] = useMaybeControlledValue<string>({
value: controlledCode,
defaultValue: defaultCode,
onChange: onCodeChange,
});
const { result, observableRef } = useSquigglePartial({
const result = useSquigglePartial({
code,
bindings,
environment,
@ -166,19 +84,9 @@ export const SquigglePartial: React.FC<SquigglePartialProps> = ({
});
return (
<div ref={observableRef}>
<SquiggleContainer>
<WrappedCodeEditor code={code} setCode={setCode} />
{result.tag !== "Ok" ? (
<SquiggleErrorAlert error={result.value} />
) : null}
</SquiggleContainer>
</div>
<SquiggleContainer>
<WrappedCodeEditor code={code} setCode={setCode} />
{result.tag !== "Ok" ? <SquiggleErrorAlert error={result.value} /> : null}
</SquiggleContainer>
);
};
export function renderSquigglePartialToDom(props: SquigglePartialProps) {
const parent = document.createElement("div");
ReactDOM.render(<SquigglePartial {...props} />, parent);
return parent;
}

View File

@ -1,15 +1,18 @@
import React, { FC, Fragment, useState, useEffect } from "react";
import ReactDOM from "react-dom";
import React, { FC, useState, useEffect, useMemo } from "react";
import { Path, useForm, UseFormRegister, useWatch } from "react-hook-form";
import * as yup from "yup";
import { useMaybeControlledValue } from "../lib/hooks";
import { yupResolver } from "@hookform/resolvers/yup";
import { Tab } from "@headlessui/react";
import {
ChartSquareBarIcon,
CheckCircleIcon,
CodeIcon,
CogIcon,
CurrencyDollarIcon,
EyeIcon,
PauseIcon,
PlayIcon,
RefreshIcon,
} from "@heroicons/react/solid";
import clsx from "clsx";
@ -20,10 +23,13 @@ import { CodeEditor } from "./CodeEditor";
import { JsonEditor } from "./JsonEditor";
import { ErrorAlert, SuccessAlert } from "./Alert";
import { SquiggleContainer } from "./SquiggleContainer";
import { Toggle } from "./ui/Toggle";
import { Checkbox } from "./ui/Checkbox";
import { StyledTab } from "./ui/StyledTab";
interface PlaygroundProps {
/** The initial squiggle string to put in the playground */
initialSquiggleString?: string;
defaultCode?: string;
/** How many pixels high is the playground */
height?: number;
/** Whether to show the types of outputs in the playground */
@ -44,104 +50,45 @@ interface PlaygroundProps {
showEditor?: boolean;
}
const schema = yup
.object()
.shape({
sampleCount: yup
.number()
.required()
.positive()
.integer()
.default(1000)
.min(10)
.max(1000000),
xyPointLength: yup
.number()
.required()
.positive()
.integer()
.default(1000)
.min(10)
.max(10000),
chartHeight: yup.number().required().positive().integer().default(350),
leftSizePercent: yup
.number()
.required()
.positive()
.integer()
.min(10)
.max(100)
.default(50),
showTypes: yup.boolean(),
showControls: yup.boolean(),
showSummary: yup.boolean(),
showEditor: yup.boolean(),
logX: yup.boolean(),
expY: yup.boolean(),
showSettingsPage: yup.boolean().default(false),
diagramStart: yup
.number()
.required()
.positive()
.integer()
.default(0)
.min(0),
diagramStop: yup
.number()
.required()
.positive()
.integer()
.default(10)
.min(0),
diagramCount: yup
.number()
.required()
.positive()
.integer()
.default(20)
.min(2),
})
.required();
const schema = yup.object({}).shape({
sampleCount: yup
.number()
.required()
.positive()
.integer()
.default(1000)
.min(10)
.max(1000000),
xyPointLength: yup
.number()
.required()
.positive()
.integer()
.default(1000)
.min(10)
.max(10000),
chartHeight: yup.number().required().positive().integer().default(350),
leftSizePercent: yup
.number()
.required()
.positive()
.integer()
.min(10)
.max(100)
.default(50),
showTypes: yup.boolean().required(),
showControls: yup.boolean().required(),
showSummary: yup.boolean().required(),
showEditor: yup.boolean().required(),
logX: yup.boolean().required(),
expY: yup.boolean().required(),
showSettingsPage: yup.boolean().default(false),
diagramStart: yup.number().required().positive().integer().default(0).min(0),
diagramStop: yup.number().required().positive().integer().default(10).min(0),
diagramCount: yup.number().required().positive().integer().default(20).min(2),
});
type StyledTabProps = {
name: string;
icon: (props: React.ComponentProps<"svg">) => JSX.Element;
};
const StyledTab: React.FC<StyledTabProps> = ({ name, icon: Icon }) => {
return (
<Tab key={name} as={Fragment}>
{({ selected }) => (
<button className="group flex rounded-md focus:outline-none focus-visible:ring-offset-gray-100">
<span
className={clsx(
"p-1 pl-2.5 pr-3.5 rounded-md flex items-center text-sm font-medium",
selected && "bg-white shadow-sm ring-1 ring-black ring-opacity-5"
)}
>
<Icon
className={clsx(
"-ml-0.5 mr-2 h-4 w-4",
selected
? "text-slate-500"
: "text-gray-400 group-hover:text-gray-900"
)}
/>
<span
className={clsx(
selected
? "text-gray-900"
: "text-gray-600 group-hover:text-gray-900"
)}
>
{name}
</span>
</span>
</button>
)}
</Tab>
);
};
type FormFields = yup.InferType<typeof schema>;
const HeadedSection: FC<{ title: string; children: React.ReactNode }> = ({
title,
@ -182,30 +129,256 @@ function InputItem<T>({
);
}
function Checkbox<T>({
name,
label,
const SamplingSettings: React.FC<{ register: UseFormRegister<FormFields> }> = ({
register,
}: {
name: Path<T>;
label: string;
register: UseFormRegister<T>;
}) {
return (
<label className="flex items-center">
<input
type="checkbox"
{...register(name)}
className="form-checkbox focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
}) => (
<div className="space-y-6 p-3 max-w-xl">
<div>
<InputItem
name="sampleCount"
type="number"
label="Sample Count"
register={register}
/>
{/* Clicking on the div makes the checkbox lose focus while mouse button is pressed, leading to annoying blinking; I couldn't figure out how to fix this. */}
<div className="ml-3 text-sm font-medium text-gray-700">{label}</div>
</label>
<div className="mt-2">
<Text>
How many samples to use for Monte Carlo simulations. This can
occasionally be overridden by specific Squiggle programs.
</Text>
</div>
</div>
<div>
<InputItem
name="xyPointLength"
type="number"
register={register}
label="Coordinate Count (For PointSet Shapes)"
/>
<div className="mt-2">
<Text>
When distributions are converted into PointSet shapes, we need to know
how many coordinates to use.
</Text>
</div>
</div>
</div>
);
const ViewSettings: React.FC<{ register: UseFormRegister<FormFields> }> = ({
register,
}) => (
<div className="space-y-6 p-3 divide-y divide-gray-200 max-w-xl">
<HeadedSection title="General Display Settings">
<div className="space-y-4">
<Checkbox
name="showEditor"
register={register}
label="Show code editor on left"
/>
<InputItem
name="chartHeight"
type="number"
register={register}
label="Chart Height (in pixels)"
/>
<Checkbox
name="showTypes"
register={register}
label="Show information about displayed types"
/>
</div>
</HeadedSection>
<div className="pt-8">
<HeadedSection title="Distribution Display Settings">
<div className="space-y-2">
<Checkbox
register={register}
name="logX"
label="Show x scale logarithmically"
/>
<Checkbox
register={register}
name="expY"
label="Show y scale exponentially"
/>
<Checkbox
register={register}
name="showControls"
label="Show toggles to adjust scale of x and y axes"
/>
<Checkbox
register={register}
name="showSummary"
label="Show summary statistics"
/>
</div>
</HeadedSection>
</div>
<div className="pt-8">
<HeadedSection title="Function Display Settings">
<div className="space-y-6">
<Text>
When displaying functions of single variables that return numbers or
distributions, we need to use defaults for the x-axis. We need to
select a minimum and maximum value of x to sample, and a number n of
the number of points to sample.
</Text>
<div className="space-y-4">
<InputItem
type="number"
name="diagramStart"
register={register}
label="Min X Value"
/>
<InputItem
type="number"
name="diagramStop"
register={register}
label="Max X Value"
/>
<InputItem
type="number"
name="diagramCount"
register={register}
label="Points between X min and X max to sample"
/>
</div>
</div>
</HeadedSection>
</div>
</div>
);
const InputVariablesSettings: React.FC<{
initialImports: any; // TODO - any json type
setImports: (imports: any) => void;
}> = ({ initialImports, setImports }) => {
const [importString, setImportString] = useState(() =>
JSON.stringify(initialImports)
);
}
const [importsAreValid, setImportsAreValid] = useState(true);
const onChange = (value: string) => {
setImportString(value);
let imports = {} as any;
try {
imports = JSON.parse(value);
setImportsAreValid(true);
} catch (e) {
setImportsAreValid(false);
}
setImports(imports);
};
return (
<div className="p-3 max-w-3xl">
<HeadedSection title="Import Variables from JSON">
<div className="space-y-6">
<Text>
You can import variables from JSON into your Squiggle code.
Variables are accessed with dollar signs. For example, "timeNow"
would be accessed as "$timeNow".
</Text>
<div className="border border-slate-200 mt-6 mb-2">
<JsonEditor
value={importString}
onChange={onChange}
oneLine={false}
showGutter={true}
height={150}
/>
</div>
<div className="p-1 pt-2">
{importsAreValid ? (
<SuccessAlert heading="Valid JSON" />
) : (
<ErrorAlert heading="Invalid JSON">
You must use valid JSON in this editor.
</ErrorAlert>
)}
</div>
</div>
</HeadedSection>
</div>
);
};
const RunControls: React.FC<{
autorunMode: boolean;
isRunning: boolean;
isStale: boolean;
onAutorunModeChange: (value: boolean) => void;
run: () => void;
}> = ({ autorunMode, isRunning, isStale, onAutorunModeChange, run }) => {
const CurrentPlayIcon = isRunning ? RefreshIcon : PlayIcon;
return (
<div className="flex space-x-1 items-center">
{autorunMode ? null : (
<button onClick={run}>
<CurrentPlayIcon
className={clsx(
"w-8 h-8",
isRunning && "animate-spin",
isStale ? "text-indigo-500" : "text-gray-400"
)}
/>
</button>
)}
<Toggle
texts={["Autorun", "Paused"]}
icons={[CheckCircleIcon, PauseIcon]}
status={autorunMode}
onChange={onAutorunModeChange}
/>
</div>
);
};
const useRunnerState = (code: string) => {
const [autorunMode, setAutorunMode] = useState(true);
const [renderedCode, setRenderedCode] = useState(code); // used in manual run mode only
const [isRunning, setIsRunning] = useState(false); // used in manual run mode only
// This part is tricky and fragile; we need to re-render first to make sure that the icon is spinning,
// and only then evaluate the squiggle code (which freezes the UI).
// Also note that `useEffect` execution order matters here.
// Hopefully it'll all go away after we make squiggle code evaluation async.
useEffect(() => {
if (renderedCode === code && isRunning) {
// It's not possible to put this after `setRenderedCode(code)` below because React would apply
// `setIsRunning` and `setRenderedCode` together and spinning icon will disappear immediately.
setIsRunning(false);
}
}, [renderedCode, code, isRunning]);
useEffect(() => {
if (!autorunMode && isRunning) {
setRenderedCode(code); // TODO - force run even if code hasn't changed
}
}, [autorunMode, code, isRunning]);
const run = () => {
// The rest will be handled by useEffects above, but we need to update the spinner first.
setIsRunning(true);
};
return {
run,
renderedCode: autorunMode ? code : renderedCode,
isRunning,
autorunMode,
setAutorunMode: (newValue: boolean) => {
if (!newValue) setRenderedCode(code);
setAutorunMode(newValue);
},
};
};
export const SquigglePlayground: FC<PlaygroundProps> = ({
initialSquiggleString = "",
defaultCode = "",
height = 500,
showTypes = false,
showControls = false,
@ -217,12 +390,14 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
onSettingsChange,
showEditor = true,
}) => {
const [uncontrolledCode, setUncontrolledCode] = useState(
initialSquiggleString
);
const [importString, setImportString] = useState("{}");
const [code, setCode] = useMaybeControlledValue({
value: controlledCode,
defaultValue: defaultCode,
onChange: onCodeChange,
});
const [imports, setImports] = useState({});
const [importsAreValid, setImportsAreValid] = useState(true);
const { register, control } = useForm({
resolver: yupResolver(schema),
defaultValues: {
@ -250,182 +425,24 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
onSettingsChange?.(vars);
}, [vars, onSettingsChange]);
const chartSettings = {
start: Number(vars.diagramStart),
stop: Number(vars.diagramStop),
count: Number(vars.diagramCount),
};
const env: environment = {
sampleCount: Number(vars.sampleCount),
xyPointLength: Number(vars.xyPointLength),
};
const getChangeJson = (r: string) => {
setImportString(r);
try {
setImports(JSON.parse(r));
setImportsAreValid(true);
} catch (e) {
setImportsAreValid(false);
}
};
const code = controlledCode ?? uncontrolledCode;
const samplingSettings = (
<div className="space-y-6 p-3 max-w-xl">
<div>
<InputItem
name="sampleCount"
type="number"
label="Sample Count"
register={register}
/>
<div className="mt-2">
<Text>
How many samples to use for Monte Carlo simulations. This can
occasionally be overridden by specific Squiggle programs.
</Text>
</div>
</div>
<div>
<InputItem
name="xyPointLength"
type="number"
register={register}
label="Coordinate Count (For PointSet Shapes)"
/>
<div className="mt-2">
<Text>
When distributions are converted into PointSet shapes, we need to
know how many coordinates to use.
</Text>
</div>
</div>
</div>
const env: environment = useMemo(
() => ({
sampleCount: Number(vars.sampleCount),
xyPointLength: Number(vars.xyPointLength),
}),
[vars.sampleCount, vars.xyPointLength]
);
const viewSettings = (
<div className="space-y-6 p-3 divide-y divide-gray-200 max-w-xl">
<HeadedSection title="General Display Settings">
<div className="space-y-4">
<Checkbox
name="showEditor"
register={register}
label="Show code editor on left"
/>
<InputItem
name="chartHeight"
type="number"
register={register}
label="Chart Height (in pixels)"
/>
<Checkbox
name="showTypes"
register={register}
label="Show information about displayed types"
/>
</div>
</HeadedSection>
<div className="pt-8">
<HeadedSection title="Distribution Display Settings">
<div className="space-y-2">
<Checkbox
register={register}
name="logX"
label="Show x scale logarithmically"
/>
<Checkbox
register={register}
name="expY"
label="Show y scale exponentially"
/>
<Checkbox
register={register}
name="showControls"
label="Show toggles to adjust scale of x and y axes"
/>
<Checkbox
register={register}
name="showSummary"
label="Show summary statistics"
/>
</div>
</HeadedSection>
</div>
<div className="pt-8">
<HeadedSection title="Function Display Settings">
<div className="space-y-6">
<Text>
When displaying functions of single variables that return numbers
or distributions, we need to use defaults for the x-axis. We need
to select a minimum and maximum value of x to sample, and a number
n of the number of points to sample.
</Text>
<div className="space-y-4">
<InputItem
type="number"
name="diagramStart"
register={register}
label="Min X Value"
/>
<InputItem
type="number"
name="diagramStop"
register={register}
label="Max X Value"
/>
<InputItem
type="number"
name="diagramCount"
register={register}
label="Points between X min and X max to sample"
/>
</div>
</div>
</HeadedSection>
</div>
</div>
);
const inputVariableSettings = (
<div className="p-3 max-w-3xl">
<HeadedSection title="Import Variables from JSON">
<div className="space-y-6">
<Text>
You can import variables from JSON into your Squiggle code.
Variables are accessed with dollar signs. For example, "timeNow"
would be accessed as "$timeNow".
</Text>
<div className="border border-slate-200 mt-6 mb-2">
<JsonEditor
value={importString}
onChange={getChangeJson}
oneLine={false}
showGutter={true}
height={150}
/>
</div>
<div className="p-1 pt-2">
{importsAreValid ? (
<SuccessAlert heading="Valid JSON" />
) : (
<ErrorAlert heading="Invalid JSON">
You must use valid JSON in this editor.
</ErrorAlert>
)}
</div>
</div>
</HeadedSection>
</div>
);
const { run, autorunMode, setAutorunMode, isRunning, renderedCode } =
useRunnerState(code);
const squiggleChart = (
<SquiggleChart
squiggleString={code}
code={renderedCode}
environment={env}
chartSettings={chartSettings}
diagramStart={Number(vars.diagramStart)}
diagramStop={Number(vars.diagramStop)}
diagramCount={Number(vars.diagramCount)}
height={vars.chartHeight}
showTypes={vars.showTypes}
showControls={vars.showControls}
@ -441,13 +458,8 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
<div className="border border-slate-200">
<CodeEditor
value={code}
onChange={(newCode) => {
if (controlledCode === undefined) {
// uncontrolled mode
setUncontrolledCode(newCode);
}
onCodeChange?.(newCode);
}}
onChange={setCode}
onSubmit={run}
oneLine={false}
showGutter={true}
height={height - 1}
@ -458,12 +470,21 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
);
const tabs = (
<Tab.Panels>
<Tab.Panel>{firstTab}</Tab.Panel>
<Tab.Panel>{samplingSettings}</Tab.Panel>
<Tab.Panel>{viewSettings}</Tab.Panel>
<Tab.Panel>{inputVariableSettings}</Tab.Panel>
</Tab.Panels>
<StyledTab.Panels>
<StyledTab.Panel>{firstTab}</StyledTab.Panel>
<StyledTab.Panel>
<SamplingSettings register={register} />
</StyledTab.Panel>
<StyledTab.Panel>
<ViewSettings register={register} />
</StyledTab.Panel>
<StyledTab.Panel>
<InputVariablesSettings
initialImports={imports}
setImports={setImports}
/>
</StyledTab.Panel>
</StyledTab.Panels>
);
const withEditor = (
@ -477,26 +498,29 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
return (
<SquiggleContainer>
<Tab.Group>
<StyledTab.Group>
<div className="pb-4">
<Tab.List className="flex w-fit p-0.5 mt-2 rounded-md bg-slate-100 hover:bg-slate-200">
<StyledTab
name={vars.showEditor ? "Code" : "Display"}
icon={vars.showEditor ? CodeIcon : EyeIcon}
<div className="flex justify-between items-center mt-2">
<StyledTab.List>
<StyledTab
name={vars.showEditor ? "Code" : "Display"}
icon={vars.showEditor ? CodeIcon : EyeIcon}
/>
<StyledTab name="Sampling Settings" icon={CogIcon} />
<StyledTab name="View Settings" icon={ChartSquareBarIcon} />
<StyledTab name="Input Variables" icon={CurrencyDollarIcon} />
</StyledTab.List>
<RunControls
autorunMode={autorunMode}
isStale={renderedCode !== code}
run={run}
isRunning={isRunning}
onAutorunModeChange={setAutorunMode}
/>
<StyledTab name="Sampling Settings" icon={CogIcon} />
<StyledTab name="View Settings" icon={ChartSquareBarIcon} />
<StyledTab name="Input Variables" icon={CurrencyDollarIcon} />
</Tab.List>
</div>
{vars.showEditor ? withEditor : withoutEditor}
</div>
</Tab.Group>
</StyledTab.Group>
</SquiggleContainer>
);
};
export function renderSquigglePlaygroundToDom(props: PlaygroundProps) {
const parent = document.createElement("div");
ReactDOM.render(<SquigglePlayground {...props} />, parent);
return parent;
}

View File

@ -0,0 +1,24 @@
import React from "react";
import { Path, UseFormRegister } from "react-hook-form";
export function Checkbox<T>({
name,
label,
register,
}: {
name: Path<T>;
label: string;
register: UseFormRegister<T>;
}) {
return (
<label className="flex items-center">
<input
type="checkbox"
{...register(name)}
className="form-checkbox focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
{/* Clicking on the div makes the checkbox lose focus while mouse button is pressed, leading to annoying blinking; I couldn't figure out how to fix this. */}
<div className="ml-3 text-sm font-medium text-gray-700">{label}</div>
</label>
);
}

View File

@ -0,0 +1,60 @@
import React, { Fragment } from "react";
import { Tab } from "@headlessui/react";
import clsx from "clsx";
type StyledTabProps = {
name: string;
icon: (props: React.ComponentProps<"svg">) => JSX.Element;
};
type StyledTabType = React.FC<StyledTabProps> & {
List: React.FC<{ children: React.ReactNode }>;
Group: typeof Tab.Group;
Panels: typeof Tab.Panels;
Panel: typeof Tab.Panel;
};
export const StyledTab: StyledTabType = ({ name, icon: Icon }) => {
return (
<Tab as={Fragment}>
{({ selected }) => (
<button className="group flex rounded-md focus:outline-none focus-visible:ring-offset-gray-100">
<span
className={clsx(
"p-1 pl-2.5 pr-3.5 rounded-md flex items-center text-sm font-medium",
selected && "bg-white shadow-sm ring-1 ring-black ring-opacity-5"
)}
>
<Icon
className={clsx(
"-ml-0.5 mr-2 h-4 w-4",
selected
? "text-slate-500"
: "text-gray-400 group-hover:text-gray-900"
)}
/>
<span
className={clsx(
selected
? "text-gray-900"
: "text-gray-600 group-hover:text-gray-900"
)}
>
{name}
</span>
</span>
</button>
)}
</Tab>
);
};
StyledTab.List = ({ children }) => (
<Tab.List className="flex w-fit p-0.5 rounded-md bg-slate-100 hover:bg-slate-200">
{children}
</Tab.List>
);
StyledTab.Group = Tab.Group;
StyledTab.Panels = Tab.Panels;
StyledTab.Panel = Tab.Panel;

View File

@ -0,0 +1,41 @@
import clsx from "clsx";
import { motion } from "framer-motion";
import React from "react";
type IconType = (props: React.ComponentProps<"svg">) => JSX.Element;
type Props = {
status: boolean;
onChange: (status: boolean) => void;
texts: [string, string];
icons: [IconType, IconType];
};
export const Toggle: React.FC<Props> = ({
texts: [onText, offText],
icons: [OnIcon, OffIcon],
status,
onChange,
}) => {
const CurrentIcon = status ? OnIcon : OffIcon;
return (
<motion.button
layout
transition={{ duration: 0.2 }}
className={clsx(
"rounded-full py-1 bg-indigo-500 text-white text-xs font-semibold flex items-center space-x-1",
status ? "bg-indigo-500" : "bg-gray-400",
status ? "pl-1 pr-3" : "pl-3 pr-1",
!status && "flex-row-reverse space-x-reverse"
)}
onClick={() => onChange(!status)}
>
<motion.div layout transition={{ duration: 0.2 }}>
<CurrentIcon className="w-6 h-6" />
</motion.div>
<motion.span layout transition={{ duration: 0.2 }}>
{status ? onText : offText}
</motion.span>
</motion.button>
);
};

View File

@ -1,14 +1,6 @@
export { SquiggleChart } from "./components/SquiggleChart";
export {
SquiggleEditor,
SquigglePartial,
renderSquiggleEditorToDom,
renderSquigglePartialToDom,
} from "./components/SquiggleEditor";
export {
SquigglePlayground,
renderSquigglePlaygroundToDom,
} from "./components/SquigglePlayground";
export { SquiggleEditor, SquigglePartial } from "./components/SquiggleEditor";
export { SquigglePlayground } from "./components/SquigglePlayground";
export { SquiggleContainer } from "./components/SquiggleContainer";
export { mergeBindings } from "@quri/squiggle-lang";

View File

@ -5,7 +5,7 @@ import {
run,
runPartial,
} from "@quri/squiggle-lang";
import { useEffect, useMemo, useRef } from "react";
import { useEffect, useMemo, useState } from "react";
type SquiggleArgs<T extends ReturnType<typeof run | typeof runPartial>> = {
code: string;
@ -19,37 +19,18 @@ const useSquiggleAny = <T extends ReturnType<typeof run | typeof runPartial>>(
args: SquiggleArgs<T>,
f: (...args: Parameters<typeof run>) => T
) => {
// We're using observable, where div elements can have a `value` property:
// https://observablehq.com/@observablehq/introduction-to-views
//
// This is here to get the 'viewof' part of:
// viewof env = cell('normal(0,1)')
// to work
const ref = useRef<
HTMLDivElement & { value?: Extract<T, { tag: "Ok" }>["value"] }
>(null);
const result: T = useMemo<T>(
() => f(args.code, args.bindings, args.environment, args.jsImports),
[f, args.code, args.bindings, args.environment, args.jsImports]
);
useEffect(() => {
if (!ref.current) return;
ref.current.value = result.tag === "Ok" ? result.value : undefined;
ref.current.dispatchEvent(new CustomEvent("input"));
}, [result]);
const { onChange } = args;
useEffect(() => {
onChange?.(result.tag === "Ok" ? result.value : undefined);
}, [result, onChange]);
return {
result, // squiggleExpression or externalBindings
observableRef: ref, // can be passed to outermost <div> if you want to use your component as an observablehq's view
};
return result;
};
export const useSquigglePartial = (
@ -61,3 +42,23 @@ export const useSquigglePartial = (
export const useSquiggle = (args: SquiggleArgs<ReturnType<typeof run>>) => {
return useSquiggleAny(args, run);
};
type ControlledValueArgs<T> = {
value?: T;
defaultValue: T;
onChange?: (x: T) => void;
};
export function useMaybeControlledValue<T>(
args: ControlledValueArgs<T>
): [T, (x: T) => void] {
let [uncontrolledValue, setUncontrolledValue] = useState(args.defaultValue);
let value = args.value ?? uncontrolledValue;
let onChange = (newValue: T) => {
if (args.value === undefined) {
// uncontrolled mode
setUncontrolledValue(newValue);
}
args.onChange?.(newValue);
};
return [value, onChange];
}

View File

@ -29,7 +29,7 @@ could be continuous, discrete or mixed.
<Story
name="Continuous Symbolic"
args={{
squiggleString: "normal(5,2)",
code: "normal(5,2)",
width,
}}
>
@ -43,7 +43,7 @@ could be continuous, discrete or mixed.
<Story
name="Continuous Pointset"
args={{
squiggleString: "toPointSet(normal(5,2))",
code: "toPointSet(normal(5,2))",
width,
}}
>
@ -57,7 +57,7 @@ could be continuous, discrete or mixed.
<Story
name="Continuous SampleSet"
args={{
squiggleString: "toSampleSet(normal(5,2), 1000)",
code: "toSampleSet(normal(5,2), 1000)",
width,
}}
>
@ -71,7 +71,7 @@ could be continuous, discrete or mixed.
<Story
name="Discrete"
args={{
squiggleString: "mx(0, 1, 3, 5, 8, 10, [0.1, 0.8, 0.5, 0.3, 0.2, 0.1])",
code: "mx(0, 1, 3, 5, 8, 10, [0.1, 0.8, 0.5, 0.3, 0.2, 0.1])",
width,
}}
>
@ -85,8 +85,7 @@ could be continuous, discrete or mixed.
<Story
name="Mixed"
args={{
squiggleString:
"mx(0, 1, 3, 5, 8, normal(8, 1), [0.1, 0.3, 0.4, 0.35, 0.2, 0.8])",
code: "mx(0, 1, 3, 5, 8, normal(8, 1), [0.1, 0.3, 0.4, 0.35, 0.2, 0.8])",
width,
}}
>
@ -103,7 +102,7 @@ to allow large and small numbers being printed cleanly.
<Story
name="Constant"
args={{
squiggleString: "500000000",
code: "500000000",
width,
}}
>
@ -117,7 +116,7 @@ to allow large and small numbers being printed cleanly.
<Story
name="Array"
args={{
squiggleString: "[normal(5,2), normal(10,1), normal(40,2), 400000]",
code: "[normal(5,2), normal(10,1), normal(40,2), 400000]",
width,
}}
>
@ -131,7 +130,7 @@ to allow large and small numbers being printed cleanly.
<Story
name="Error"
args={{
squiggleString: "f(x) = normal(",
code: "f(x) = normal(",
width,
}}
>
@ -145,7 +144,7 @@ to allow large and small numbers being printed cleanly.
<Story
name="Boolean"
args={{
squiggleString: "3 == 3",
code: "3 == 3",
width,
}}
>
@ -159,7 +158,7 @@ to allow large and small numbers being printed cleanly.
<Story
name="Function to Distribution"
args={{
squiggleString: "foo(t) = normal(t,2)*normal(5,3); foo",
code: "foo(t) = normal(t,2)*normal(5,3); foo",
width,
}}
>
@ -173,7 +172,7 @@ to allow large and small numbers being printed cleanly.
<Story
name="Function to Number"
args={{
squiggleString: "foo(t) = t^2; foo",
code: "foo(t) = t^2; foo",
width,
}}
>
@ -187,7 +186,7 @@ to allow large and small numbers being printed cleanly.
<Story
name="Record"
args={{
squiggleString: "{foo: 35 to 50, bar: [1,2,3]}",
code: "{foo: 35 to 50, bar: [1,2,3]}",
width,
}}
>
@ -201,7 +200,7 @@ to allow large and small numbers being printed cleanly.
<Story
name="String"
args={{
squiggleString: '"Lucky day!"',
code: '"Lucky day!"',
width,
}}
>

View File

@ -14,7 +14,20 @@ the distribution.
<Story
name="Normal"
args={{
initialSquiggleString: "normal(5,2)",
defaultCode: "normal(5,2)",
}}
>
{Template.bind({})}
</Story>
</Canvas>
It's also possible to create a controlled version of the same component
<Canvas>
<Story
name="Controlled"
args={{
code: "normal(5,2)",
}}
>
{Template.bind({})}
@ -27,7 +40,7 @@ You can also name variables like so:
<Story
name="Variables"
args={{
initialSquiggleString: "x = 2\nnormal(x,2)",
defaultCode: "x = 2\nnormal(x,2)",
}}
>
{Template.bind({})}

View File

@ -15,7 +15,7 @@ instead returns bindings that can be used by further Squiggle Editors.
<Story
name="Standalone"
args={{
initialSquiggleString: "x = normal(5,2)",
defaultCode: "x = normal(5,2)",
}}
>
{Template.bind({})}
@ -36,12 +36,12 @@ instead returns bindings that can be used by further Squiggle Editors.
<>
<SquigglePartial
{...props}
initialSquiggleString={props.initialPartialString}
defaultCode={props.initialPartialString}
onChange={setBindings}
/>
<SquiggleEditor
{...props}
initialSquiggleString={props.initialEditorString}
defaultCode={props.initialEditorString}
bindings={bindings}
/>
</>

View File

@ -14,7 +14,7 @@ including sampling settings, in squiggle.
<Story
name="Normal"
args={{
initialSquiggleString: "normal(5,2)",
defaultCode: "normal(5,2)",
height: 800,
}}
>

View File

@ -77,6 +77,23 @@ describe("(Symbolic) mean", () => {
meanValue->unpackFloat->expect->ExpectJs.toBeFalsy
})
testAll(
"of beta distributions from mean and standard dev",
list{(0.39, 0.1), (0.08, 0.1), (0.8, 0.3)},
tup => {
let (mean, stdev) = tup
let betaDistribution = SymbolicDist.Beta.fromMeanAndStdev(mean, stdev)
let meanValue =
betaDistribution->E.R2.fmap(d =>
run(FromDist(ToFloat(#Mean), d->DistributionTypes.Symbolic))
)
switch meanValue {
| Ok(value) => value->unpackFloat->expect->toBeCloseTo(mean)
| Error(err) => err->expect->toBe("shouldn't happen")
}
},
)
testAll(
"of lognormal distributions",
list{(2.0, 4.0), (1e-7, 1e-2), (-1e6, 10.0), (1e3, -1e2), (-1e8, -1e4), (1e2, 1e-5)},

View File

@ -2,7 +2,7 @@
module ErrorValue = Reducer_ErrorValue
module ExternalExpressionValue = ReducerInterface.ExternalExpressionValue
module InternalExpressionValue = ReducerInterface.InternalExpressionValue
module Module = Reducer_Category_Module
module Module = Reducer_Module
let removeDefaultsInternal = (iev: InternalExpressionValue.t) => {
switch iev {

View File

@ -0,0 +1,100 @@
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module ExpressionT = Reducer_Expression_T
module Module = Reducer_Module
module Bindings = Reducer_Module
module ErrorValue = Reducer_ErrorValue
open Jest
open Expect
// ----------------------
// --- Start of Module File
// ----------------------
module FooImplementation = {
// As this is a Rescript module, functions can use other functions in this module
// and in other stdLib modules implemented this way.
// Embedding function definitions in to switch statements is a bad practice
// - to reduce line count or to
let fooNumber = 0.0
let fooString = "Foo String"
let fooBool = true
let makeFoo = (a: string, b: string, _environment): string => `I am ${a}-foo and I am ${b}-foo`
let makeBar = (a: float, b: float, _environment): string =>
`I am ${a->Js.Float.toString}-bar and I am ${b->Js.Float.toString}-bar`
}
// There is a potential for type modules to define lift functions
// for their own type to get rid of switch statements.
module FooFFI = {
let makeFoo: ExpressionT.optionFfiFn = (args: array<InternalExpressionValue.t>, environment) => {
switch args {
| [IEvString(a), IEvString(b)] => FooImplementation.makeFoo(a, b, environment)->IEvString->Some
| _ => None
}
}
let makeBar: ExpressionT.optionFfiFn = (args: array<InternalExpressionValue.t>, environment) =>
switch args {
| [IEvNumber(a), IEvNumber(b)] => FooImplementation.makeBar(a, b, environment)->IEvString->Some
| _ => None
}
}
let fooModule: Module.t =
Module.emptyStdLib
->Module.defineNumber("fooNumber", FooImplementation.fooNumber)
->Module.defineString("fooString", FooImplementation.fooString)
->Module.defineBool("fooBool", FooImplementation.fooBool)
->Module.defineFunction("makeFoo", FooFFI.makeFoo)
->Module.defineFunction("makeBar", FooFFI.makeBar)
let makeBindings = (prevBindings: Bindings.t): Bindings.t =>
prevBindings->Module.defineModule("Foo", fooModule)
// ----------------------
// --- End of Module File
// ----------------------
let stdLibWithFoo = Bindings.emptyBindings->makeBindings
let evalWithFoo = sourceCode =>
Reducer_Expression.parse(sourceCode)->Belt.Result.flatMap(expr =>
Reducer_Expression.reduceExpression(
expr,
stdLibWithFoo,
InternalExpressionValue.defaultEnvironment,
)
)
let evalToStringResultWithFoo = sourceCode =>
evalWithFoo(sourceCode)->InternalExpressionValue.toStringResult
describe("Module", () => {
test("fooNumber", () => {
let result = evalToStringResultWithFoo("Foo.fooNumber")
expect(result)->toEqual("Ok(0)")
})
test("fooString", () => {
let result = evalToStringResultWithFoo("Foo.fooString")
expect(result)->toEqual("Ok('Foo String')")
})
test("fooBool", () => {
let result = evalToStringResultWithFoo("Foo.fooBool")
expect(result)->toEqual("Ok(true)")
})
test("fooBool", () => {
let result = evalToStringResultWithFoo("Foo.fooBool")
expect(result)->toEqual("Ok(true)")
})
test("makeFoo", () => {
let result = evalToStringResultWithFoo("Foo.makeFoo('a', 'b')")
expect(result)->toEqual("Ok('I am a-foo and I am b-foo')")
})
test("makeFoo wrong arguments", () => {
let result = evalToStringResultWithFoo("Foo.makeFoo(1, 2)")
// Notice the error with types
expect(result)->toEqual("Error(Function not found: makeFoo(Number,Number))")
})
test("makeBar", () => {
let result = evalToStringResultWithFoo("Foo.makeBar(1, 2)")
expect(result)->toEqual("Ok('I am 1-bar and I am 2-bar')")
})
})

View File

@ -8,7 +8,7 @@ module InternalExpressionValue = ReducerInterface.InternalExpressionValue
module ExpressionWithContext = Reducer_ExpressionWithContext
module Macro = Reducer_Expression_Macro
module T = Reducer_Expression_T
module Module = Reducer_Category_Module
module Module = Reducer_Module
let testMacro_ = (
tester,

View File

@ -42,7 +42,7 @@
"@stdlib/stats": "^0.0.13",
"jstat": "^1.9.5",
"lodash": "^4.17.21",
"mathjs": "^10.6.3",
"mathjs": "^10.6.4",
"pdfast": "^0.2.0"
},
"devDependencies": {
@ -65,7 +65,7 @@
"rescript-fast-check": "^1.1.1",
"ts-jest": "^27.1.4",
"ts-loader": "^9.3.0",
"ts-node": "^10.8.1",
"ts-node": "^10.8.2",
"typescript": "^4.7.4",
"webpack": "^5.73.0",
"webpack-cli": "^4.10.0"

View File

@ -172,6 +172,15 @@ function createTsExport(
return tag("lambdaDeclaration", x.value);
case "EvTypeIdentifier":
return tag("typeIdentifier", x.value);
case "EvType":
let typeResult: tagged<"type", { [key: string]: squiggleExpression }> =
tag(
"type",
_.mapValues(x.value, (x: unknown) =>
convertRawToTypescript(x as rescriptExport, environment)
)
);
return typeResult;
case "EvModule":
let moduleResult: tagged<
"module",

View File

@ -129,6 +129,7 @@ export type squiggleExpression =
| tagged<"timeDuration", number>
| tagged<"lambdaDeclaration", lambdaDeclaration>
| tagged<"record", { [key: string]: squiggleExpression }>
| tagged<"type", { [key: string]: squiggleExpression }>
| tagged<"typeIdentifier", string>
| tagged<"module", { [key: string]: squiggleExpression }>;

View File

@ -124,6 +124,26 @@ module Beta = {
let sample = (t: t) => Jstat.Beta.sample(t.alpha, t.beta)
let mean = (t: t) => Ok(Jstat.Beta.mean(t.alpha, t.beta))
let toString = ({alpha, beta}: t) => j`Beta($alpha,$beta)`
let fromMeanAndSampleSize = (mean, sampleSize) => {
// https://en.wikipedia.org/wiki/Beta_distribution#Mean_and_sample_size
let alpha = mean *. sampleSize
let beta = (1.0 -. mean) *. sampleSize
make(alpha, beta)
}
let fromMeanAndStdev = (mean, stdev) => {
// https://en.wikipedia.org/wiki/Beta_distribution#Mean_and_variance
if !(0.0 < stdev && stdev <= 0.5) {
Error("Stdev must be in in between 0 and 0.5.")
} else if !(0.0 <= mean && mean <= 1.0) {
Error("Mean must be in between 0 and 1.0.")
} else {
let var = stdev *. stdev
let sampleSize = mean *. (1.0 -. mean) /. var -. 1.0
fromMeanAndSampleSize(mean, sampleSize)
}
}
}
module Lognormal = {

View File

@ -117,8 +117,12 @@ lognormal({mean: 5, stdev: 2})`,
),
Function.make(
~name="Beta",
~examples=`beta(20, 25)`,
~definitions=[TwoArgDist.make("beta", twoArgs(SymbolicDist.Beta.make))],
~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(

View File

@ -1,16 +0,0 @@
module ExpressionValue = ReducerInterface_InternalExpressionValue
module ExpressionT = Reducer_Expression_T
open ExpressionValue
let isOfResolvedType = (aType, aValue) => {
let caseTypeIdentifier = (aTypeIdentifier0, aValue) => {
let valueType = aValue->valueToValueType->valueTypeToString->Js.String2.toLowerCase
let aTypeIdentifier = aTypeIdentifier0->Js.String2.toLowerCase
aTypeIdentifier === valueType
}
switch aType {
| IEvTypeIdentifier(aTypeIdentifier) => caseTypeIdentifier(aTypeIdentifier, aValue)
| _ => false
}
}

View File

@ -3,8 +3,9 @@ module ExpressionT = Reducer_Expression_T
module ExternalLibrary = ReducerInterface.ExternalLibrary
module Lambda = Reducer_Expression_Lambda
module MathJs = Reducer_MathJs
module Module = Reducer_Category_Module
module Module = Reducer_Module
module Result = Belt.Result
module TypeBuilder = Reducer_Type_TypeBuilder
open ReducerInterface_InternalExpressionValue
open Reducer_ErrorValue
@ -166,59 +167,6 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
)
}
let typeModifier_memberOf = (aType, anArray) => {
let newRecord = Belt.Map.String.fromArray([
("typeTag", IEvString("typeIdentifier")),
("typeIdentifier", aType),
])
newRecord->Belt.Map.String.set("memberOf", anArray)->IEvRecord->Ok
}
let typeModifier_memberOf_update = (aRecord, anArray) => {
aRecord->Belt.Map.String.set("memberOf", anArray)->IEvRecord->Ok
}
let typeModifier_min = (aType, value) => {
let newRecord = Belt.Map.String.fromArray([
("typeTag", IEvString("typeIdentifier")),
("typeIdentifier", aType),
])
newRecord->Belt.Map.String.set("min", value)->IEvRecord->Ok
}
let typeModifier_min_update = (aRecord, value) => {
aRecord->Belt.Map.String.set("min", value)->IEvRecord->Ok
}
let typeModifier_max = (aType, value) => {
let newRecord = Belt.Map.String.fromArray([
("typeTag", IEvString("typeIdentifier")),
("typeIdentifier", aType),
])
newRecord->Belt.Map.String.set("max", value)->IEvRecord->Ok
}
let typeModifier_max_update = (aRecord, value) =>
aRecord->Belt.Map.String.set("max", value)->IEvRecord->Ok
let typeModifier_opaque_update = aRecord =>
aRecord->Belt.Map.String.set("opaque", IEvBool(true))->IEvRecord->Ok
let typeOr = evArray => {
let newRecord = Belt.Map.String.fromArray([
("typeTag", IEvString("typeOr")),
("typeOr", evArray),
])
newRecord->IEvRecord->Ok
}
let typeFunction = anArray => {
let output = Belt.Array.getUnsafe(anArray, Js.Array2.length(anArray) - 1)
let inputs = Js.Array2.slice(anArray, ~start=0, ~end_=-1)
let newRecord = Belt.Map.String.fromArray([
("typeTag", IEvString("typeFunction")),
("inputs", IEvArray(inputs)),
("output", output),
])
newRecord->IEvRecord->Ok
}
switch call {
| ("$_atIndex_$", [IEvArray(aValueArray), IEvNumber(fIndex)]) => arrayAtIndex(aValueArray, fIndex)
| ("$_atIndex_$", [IEvModule(dict), IEvString(sIndex)]) => moduleAtIndex(dict, sIndex)
@ -233,20 +181,24 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
| ("$_setTypeOfBindings_$", [IEvModule(nameSpace), IEvSymbol(symbol), value]) =>
doSetTypeOfBindings(nameSpace, symbol, value)
| ("$_typeModifier_memberOf_$", [IEvTypeIdentifier(typeIdentifier), IEvArray(arr)]) =>
typeModifier_memberOf(IEvTypeIdentifier(typeIdentifier), IEvArray(arr))
| ("$_typeModifier_memberOf_$", [IEvRecord(typeRecord), IEvArray(arr)]) =>
typeModifier_memberOf_update(typeRecord, IEvArray(arr))
TypeBuilder.typeModifier_memberOf(IEvTypeIdentifier(typeIdentifier), IEvArray(arr))
| ("$_typeModifier_memberOf_$", [IEvType(typeRecord), IEvArray(arr)]) =>
TypeBuilder.typeModifier_memberOf_update(typeRecord, IEvArray(arr))
| ("$_typeModifier_min_$", [IEvTypeIdentifier(typeIdentifier), value]) =>
typeModifier_min(IEvTypeIdentifier(typeIdentifier), value)
| ("$_typeModifier_min_$", [IEvRecord(typeRecord), value]) =>
typeModifier_min_update(typeRecord, value)
TypeBuilder.typeModifier_min(IEvTypeIdentifier(typeIdentifier), value)
| ("$_typeModifier_min_$", [IEvType(typeRecord), value]) =>
TypeBuilder.typeModifier_min_update(typeRecord, value)
| ("$_typeModifier_max_$", [IEvTypeIdentifier(typeIdentifier), value]) =>
typeModifier_max(IEvTypeIdentifier(typeIdentifier), value)
| ("$_typeModifier_max_$", [IEvRecord(typeRecord), value]) =>
typeModifier_max_update(typeRecord, value)
| ("$_typeModifier_opaque_$", [IEvRecord(typeRecord)]) => typeModifier_opaque_update(typeRecord)
| ("$_typeOr_$", [IEvArray(arr)]) => typeOr(IEvArray(arr))
| ("$_typeFunction_$", [IEvArray(arr)]) => typeFunction(arr)
TypeBuilder.typeModifier_max(IEvTypeIdentifier(typeIdentifier), value)
| ("$_typeModifier_max_$", [IEvType(typeRecord), value]) =>
TypeBuilder.typeModifier_max_update(typeRecord, value)
| ("$_typeModifier_opaque_$", [IEvType(typeRecord)]) =>
TypeBuilder.typeModifier_opaque_update(typeRecord)
| ("$_typeOr_$", [IEvArray(arr)]) => TypeBuilder.typeOr(IEvArray(arr))
| ("$_typeFunction_$", [IEvArray(arr)]) => TypeBuilder.typeFunction(arr)
| ("$_typeTuple_$", [IEvArray(elems)]) => TypeBuilder.typeTuple(elems)
| ("$_typeArray_$", [elem]) => TypeBuilder.typeArray(elem)
| ("$_typeRecord_$", [IEvArray(arrayOfPairs)]) => TypeBuilder.typeRecord(arrayOfPairs)
| ("concat", [IEvArray(aValueArray), IEvArray(bValueArray)]) =>
doAddArray(aValueArray, bValueArray)
| ("concat", [IEvString(aValueString), IEvString(bValueString)]) =>

View File

@ -9,7 +9,7 @@ module ExpressionBuilder = Reducer_Expression_ExpressionBuilder
module ExpressionT = Reducer_Expression_T
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module ExpressionWithContext = Reducer_ExpressionWithContext
module Module = Reducer_Category_Module
module Module = Reducer_Module
module Result = Belt.Result
open Reducer_Expression_ExpressionBuilder

View File

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

View File

@ -6,7 +6,7 @@ module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Lambda = Reducer_Expression_Lambda
module Macro = Reducer_Expression_Macro
module MathJs = Reducer_MathJs
module Module = Reducer_Category_Module
module Module = Reducer_Module
module Result = Belt.Result
module T = Reducer_Expression_T

View File

@ -3,7 +3,7 @@ module ErrorValue = Reducer_ErrorValue
module ExpressionT = Reducer_Expression_T
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Result = Belt.Result
module Module = Reducer_Category_Module
module Module = Reducer_Module
type bindings = ExpressionT.bindings
type context = bindings

View File

@ -2,7 +2,7 @@ module ErrorValue = Reducer_ErrorValue
module ExpressionT = Reducer_Expression_T
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Result = Belt.Result
module Module = Reducer_Category_Module
module Module = Reducer_Module
type errorValue = Reducer_ErrorValue.errorValue
type expression = ExpressionT.expression

View File

@ -2,7 +2,7 @@ module BBindingsReplacer = Reducer_Expression_BindingsReplacer
module BErrorValue = Reducer_ErrorValue
module BExpressionT = Reducer_Expression_T
module BInternalExpressionValue = ReducerInterface_InternalExpressionValue
module BModule = Reducer_Category_Module
module BModule = Reducer_Module
type errorValue = BErrorValue.errorValue
type expression = BExpressionT.expression
@ -10,8 +10,6 @@ type expressionOrFFI = BExpressionT.expressionOrFFI
type ffiFn = BExpressionT.ffiFn
type internalCode = ReducerInterface_InternalExpressionValue.internalCode
external castExpressionToInternalCode: expressionOrFFI => internalCode = "%identity"
let eArray = anArray => anArray->BInternalExpressionValue.IEvArray->BExpressionT.EValue
let eArrayString = anArray => anArray->BInternalExpressionValue.IEvArrayString->BExpressionT.EValue
@ -37,17 +35,12 @@ let eLambda = (
BInternalExpressionValue.IEvLambda({
parameters: parameters,
context: context,
body: NotFFI(expr)->castExpressionToInternalCode,
body: NotFFI(expr)->BModule.castExpressionToInternalCode,
})->BExpressionT.EValue
}
let eLambdaFFI = (parameters: array<string>, ffiFn: ffiFn) => {
let context = BModule.emptyModule
BInternalExpressionValue.IEvLambda({
parameters: parameters,
context: context,
body: FFI(ffiFn)->castExpressionToInternalCode,
})->BExpressionT.EValue
let eLambdaFFI = (ffiFn: ffiFn) => {
ffiFn->BModule.eLambdaFFIValue->BExpressionT.EValue
}
let eNumber = aNumber => aNumber->BInternalExpressionValue.IEvNumber->BExpressionT.EValue
@ -81,6 +74,9 @@ let eBindExpression = (bindingExpr: expression, expression: expression): express
let eBindExpressionDefault = (expression: expression): expression =>
eFunction("$$_bindExpression_$$", list{expression})
let eTernary = (truth: expression, trueCase: expression, falseCase: expression): expression =>
eFunction("$$_ternary_$$", list{truth, trueCase, falseCase})
let eIdentifier = (name: string): expression =>
name->BInternalExpressionValue.IEvSymbol->BExpressionT.EValue

View File

@ -3,14 +3,13 @@ module ErrorValue = Reducer_ErrorValue
module ExpressionBuilder = Reducer_Expression_ExpressionBuilder
module ExpressionT = Reducer_Expression_T
module ExpressionValue = ReducerInterface_InternalExpressionValue
module Module = Reducer_Category_Module
module Module = Reducer_Module
module Result = Belt.Result
type environment = ReducerInterface_InternalExpressionValue.environment
type expression = ExpressionT.expression
type expressionOrFFI = ExpressionT.expressionOrFFI
type internalExpressionValue = ReducerInterface_InternalExpressionValue.t
// type tmpExternalBindings = ReducerInterface_InternalExpressionValue.tmpExternalBindings
type internalCode = ReducerInterface_InternalExpressionValue.internalCode
external castInternalCodeToExpression: internalCode => expressionOrFFI = "%identity"
@ -19,12 +18,19 @@ let checkArity = (
lambdaValue: ExpressionValue.lambdaValue,
args: list<internalExpressionValue>,
) => {
let argsLength = Belt.List.length(args)
let parametersLength = Js.Array2.length(lambdaValue.parameters)
if argsLength !== parametersLength {
ErrorValue.REArityError(None, parametersLength, argsLength)->Error
} else {
args->Ok
let reallyCheck = {
let argsLength = Belt.List.length(args)
let parametersLength = Js.Array2.length(lambdaValue.parameters)
if argsLength !== parametersLength {
ErrorValue.REArityError(None, parametersLength, argsLength)->Error
} else {
args->Ok
}
}
let exprOrFFI = castInternalCodeToExpression(lambdaValue.body)
switch exprOrFFI {
| NotFFI(_) => reallyCheck
| FFI(_) => args->Ok
}
}

View File

@ -71,6 +71,8 @@ type ffiFn = (
environment,
) => result<internalExpressionValue, Reducer_ErrorValue.errorValue>
type optionFfiFn = (array<internalExpressionValue>, environment) => option<internalExpressionValue>
type expressionOrFFI =
| NotFFI(expression)
| FFI(ffiFn)

View File

@ -1,5 +1,8 @@
module ExpressionT = Reducer_Expression_T
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
open Reducer_ErrorValue
open ReducerInterface_InternalExpressionValue
let expressionValueToString = toString
type t = ReducerInterface_InternalExpressionValue.nameSpace
@ -66,7 +69,8 @@ let set = (nameSpace: t, id: string, value): t => {
Belt.Map.String.set(container, id, value)->NameSpace
}
let emptyModule: t = NameSpace(Belt.Map.String.empty)
let emptyModule: t = NameSpace(emptyMap)
let emptyBindings = emptyModule
let fromTypeScriptBindings = ReducerInterface_InternalExpressionValue.nameSpaceFromTypeScriptBindings
let toTypeScriptBindings = ReducerInterface_InternalExpressionValue.nameSpaceToTypeScriptBindings
@ -100,13 +104,59 @@ let removeOther = (nameSpace: t, other: t): t => {
})->NameSpace
}
external castExpressionToInternalCode: ExpressionT.expressionOrFFI => internalCode = "%identity"
let eLambdaFFIValue = (ffiFn: ExpressionT.ffiFn) => {
IEvLambda({
parameters: [],
context: emptyModule,
body: FFI(ffiFn)->castExpressionToInternalCode,
})
}
let functionNotFoundError = (call: functionCall) =>
REFunctionNotFound(call->functionCallToCallSignature->functionCallSignatureToString)->Error
let functionNotFoundErrorFFIFn = (functionName: string): ExpressionT.ffiFn => {
(args: array<internalExpressionValue>, _environment: environment): result<
internalExpressionValue,
errorValue,
> => {
let call = (functionName, args)
functionNotFoundError(call)
}
}
let convertOptionToFfiFn = (
myFunctionName: string,
myFunction: ExpressionT.optionFfiFn,
): ExpressionT.ffiFn => {
(args: array<InternalExpressionValue.t>, environment) => {
myFunction(args, environment)
->Belt.Option.map(v => v->Ok)
->Belt.Option.getWithDefault(functionNotFoundErrorFFIFn(myFunctionName)(args, environment))
}
}
// -- Module definition
let define = (nameSpace: t, identifier: string, ev: internalExpressionValue): t => {
let NameSpace(container) = nameSpace
Belt.Map.String.set(container, identifier, ev)->NameSpace // TODO build lambda for polymorphic functions here
Belt.Map.String.set(container, identifier, ev)->NameSpace
}
let defineNumber = (nameSpace: t, identifier: string, value: float): t =>
nameSpace->define(identifier, IEvNumber(value))
let defineString = (nameSpace: t, identifier: string, value: string): t =>
nameSpace->define(identifier, IEvString(value))
let defineBool = (nameSpace: t, identifier: string, value: bool): t =>
nameSpace->define(identifier, IEvBool(value))
let defineModule = (nameSpace: t, identifier: string, value: t): t =>
nameSpace->define(identifier, toExpressionValue(value))
let defineFunction = (nameSpace: t, identifier: string, value: ExpressionT.optionFfiFn): t => {
nameSpace->define(identifier, convertOptionToFfiFn(identifier, value)->eLambdaFFIValue)
}
let emptyStdLib: t = emptyModule->defineBool("stdlib", true)

View File

@ -0,0 +1,14 @@
module ExpressionBuilder = Reducer_Expression_ExpressionBuilder
module ExpressionT = Reducer_Expression_T
type expression = ExpressionT.expression
let defaultCaseFFI = (functionName: string): expression => {
ExpressionBuilder.eLambdaFFI(Reducer_Module.functionNotFoundErrorFFIFn(functionName))
}
let addGuard = (
guard: expression,
expression: expression,
previousExpression: expression,
): expression => ExpressionBuilder.eTernary(guard, expression, previousExpression)

View File

@ -154,9 +154,7 @@ unary
unaryOperator "unary operator"
= ('-' / '.-' / '!' )
postOperator = indexedValue
indexedValue
postOperator
= collectionElement
/ atom
@ -343,11 +341,18 @@ typeModifierExpression = head:basicType tail:(_ '<-' _nl @typeModifier)*
/ modifier:identifier _ noArguments
{ return {modifier: modifier, args: []}; }
basicType = typeConstructor / typeArray / typeRecord / typeInParanthesis / typeIdentifier
basicType = typeConstructor / typeArray / typeTuple / typeRecord / typeInParanthesis / typeIdentifier
typeArray = '[' _nl elem:typeExpression _nl ']'
{return h.apply('$_typeArray_$', elem)}
typeTuple = '[' _nl elems:array_typeTupleArguments _nl ']'
{ return h.apply('$_typeTuple_$', h.constructArray(elems))}
array_typeTupleArguments
= head:typeExpression tail:(_ ',' _nl @typeExpression)*
{ return [head, ...tail]; }
typeRecord = '{' _nl elems:array_typeRecordArguments _nl '}'
{ return h.apply('$_typeRecord_$', h.constructRecord(elems)); }
@ -375,9 +380,3 @@ typeOfStatement = identifier:identifier _ ':' _nl typeExpression:typeExpression
{ return h.makeFunctionCall('$_typeOf_$', [identifier, typeExpression])}
typeInParanthesis = '(' _nl typeExpression:typeExpression _nl ')' {return typeExpression}
// TODO: min max example
// TODO: Example of foo = {a: 2, b: 5}; type fooKeys = string $ memberOf(foo->keys)
// TODO: Example of memberOf( [1,2,3] )
// TODO: Example of $
// TODO: Cons(a, list) | EmptyList

View File

@ -5,7 +5,11 @@ type node = {"type": string}
@module("./Reducer_Peggy_GeneratedParser.js") external parse__: string => node = "parse"
let syntaxErrorToLocation: Js.Exn.t => Reducer_ErrorValue.location = error => %raw(`error.location`)
type withLocation = {"location": Reducer_ErrorValue.location}
external castWithLocation: Js.Exn.t => withLocation = "%identity"
let syntaxErrorToLocation = (error: Js.Exn.t): Reducer_ErrorValue.location =>
castWithLocation(error)["location"]
@genType
let parse = (expr: string): result<node, errorValue> =>

View File

@ -0,0 +1,82 @@
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
open InternalExpressionValue
type rec iType =
| ItTypeIdentifier(string)
| ItModifiedType({modifiedType: iType})
| ItTypeOr({typeOr: array<iType>})
| ItTypeFunction({inputs: array<iType>, output: iType})
| ItTypeArray({element: iType})
| ItTypeTuple({elements: array<iType>})
| ItTypeRecord({properties: Belt.Map.String.t<iType>})
let rec fromTypeMap = typeMap => {
let default = IEvString("")
let evTypeTag: InternalExpressionValue.t = Belt.Map.String.getWithDefault(
typeMap,
"typeTag",
default,
)
let evTypeIdentifier: InternalExpressionValue.t = Belt.Map.String.getWithDefault(
typeMap,
"typeIdentifier",
default,
)
let evTypeOr: InternalExpressionValue.t = Belt.Map.String.getWithDefault(
typeMap,
"typeOr",
default,
)
let evInputs: InternalExpressionValue.t = Belt.Map.String.getWithDefault(
typeMap,
"inputs",
default,
)
let evOutput: InternalExpressionValue.t = Belt.Map.String.getWithDefault(
typeMap,
"output",
default,
)
let evElement: InternalExpressionValue.t = Belt.Map.String.getWithDefault(
typeMap,
"element",
default,
)
let evElements: InternalExpressionValue.t = Belt.Map.String.getWithDefault(
typeMap,
"elements",
default,
)
let evProperties: InternalExpressionValue.t = Belt.Map.String.getWithDefault(
typeMap,
"properties",
default,
)
//TODO: map type modifiers
switch evTypeTag {
| IEvString("typeIdentifier") => ItModifiedType({modifiedType: 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)
}
}
and fromIEvValue = (ievValue: InternalExpressionValue.t) =>
switch ievValue {
| IEvTypeIdentifier(typeIdentifier) => ItTypeIdentifier({typeIdentifier})
| IEvType(typeMap) => fromTypeMap(typeMap)
| _ => raise(Reducer_Exception.ImpossibleException)
}
and fromIEvArray = (ievArray: InternalExpressionValue.t) =>
switch ievArray {
| IEvArray(array) => array->Belt.Array.map(fromIEvValue)
| _ => raise(Reducer_Exception.ImpossibleException)
}
and fromIEvRecord = (ievRecord: InternalExpressionValue.t) =>
switch ievRecord {
| IEvRecord(record) => record->Belt.Map.String.map(fromIEvValue)
| _ => raise(Reducer_Exception.ImpossibleException)
}

View File

@ -0,0 +1,88 @@
open ReducerInterface_InternalExpressionValue
let typeModifier_memberOf = (aType, anArray) => {
let newRecord = Belt.Map.String.fromArray([
("typeTag", IEvString("typeIdentifier")),
("typeIdentifier", aType),
])
newRecord->Belt.Map.String.set("memberOf", anArray)->IEvType->Ok
}
let typeModifier_memberOf_update = (aRecord, anArray) => {
aRecord->Belt.Map.String.set("memberOf", anArray)->IEvType->Ok
}
let typeModifier_min = (aType, value) => {
let newRecord = Belt.Map.String.fromArray([
("typeTag", IEvString("typeIdentifier")),
("typeIdentifier", aType),
])
newRecord->Belt.Map.String.set("min", value)->IEvType->Ok
}
let typeModifier_min_update = (aRecord, value) => {
aRecord->Belt.Map.String.set("min", value)->IEvType->Ok
}
let typeModifier_max = (aType, value) => {
let newRecord = Belt.Map.String.fromArray([
("typeTag", IEvString("typeIdentifier")),
("typeIdentifier", aType),
])
newRecord->Belt.Map.String.set("max", value)->IEvType->Ok
}
let typeModifier_max_update = (aRecord, value) =>
aRecord->Belt.Map.String.set("max", value)->IEvType->Ok
let typeModifier_opaque_update = aRecord =>
aRecord->Belt.Map.String.set("opaque", IEvBool(true))->IEvType->Ok
let typeOr = evArray => {
let newRecord = Belt.Map.String.fromArray([("typeTag", IEvString("typeOr")), ("typeOr", evArray)])
newRecord->IEvType->Ok
}
let typeFunction = anArray => {
let output = Belt.Array.getUnsafe(anArray, Js.Array2.length(anArray) - 1)
let inputs = Js.Array2.slice(anArray, ~start=0, ~end_=-1)
let newRecord = Belt.Map.String.fromArray([
("typeTag", IEvString("typeFunction")),
("inputs", IEvArray(inputs)),
("output", output),
])
newRecord->IEvType->Ok
}
let typeArray = element => {
let newRecord = Belt.Map.String.fromArray([
("typeTag", IEvString("typeTuple")),
("element", element),
])
newRecord->IEvType->Ok
}
let typeTuple = anArray => {
let newRecord = Belt.Map.String.fromArray([
("typeTag", IEvString("typeArray")),
("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 newRecord = Belt.Map.String.fromArray([
("typeTag", IEvString("typeRecord")),
("properties", newProperties),
])
newRecord->IEvType->Ok
}

View File

@ -0,0 +1,81 @@
module ExpressionT = Reducer_Expression_T
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module T = Reducer_Type_T
module TypeBuilder = Reducer_Type_TypeBuilder
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 caseTypeIdentifier = (anUpperTypeName, aValue) => {
let aTypeName = anUpperTypeName->Js.String2.toLowerCase
let valueTypeName = aValue->valueToValueType->valueTypeToString->Js.String2.toLowerCase
switch aTypeName === valueTypeName {
| true => Ok(true)
| false => TypeError(anIType, aValue)->Error
}
}
let _caseRecord = (anIType, evValue, propertyMap, map) => {
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
}
)
})
}
let _caseArray = (anIType, evValue, elementType, anArray) => {
Belt.Array.reduceWithIndex(anArray, Ok(true), (acc, element, index) => {
switch isOfResolvedIType(elementType, element) {
| Ok(_) => acc
| Error(_) => TypeErrorWithPosition(anIType, evValue, index)->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)
}
}
let isOfResolvedType = (aType: InternalExpressionValue.t, aValue): result<bool, typeErrorValue> =>
aType->T.fromIEvValue->isOfResolvedIType(aValue)
// 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 compileTypeExpression = (typeExpression: string, bindings: ExpressionT.bindings, reducerFn: ExpressionT.reducerFn) => {
// statement = `type compiled=${typeExpression}`
// }
//TODO: asGuard

View File

@ -24,6 +24,7 @@ type rec externalExpressionValue =
| EvDeclaration(lambdaDeclaration)
| EvTypeIdentifier(string)
| EvModule(record)
| EvType(record)
and record = Js.Dict.t<externalExpressionValue>
and externalBindings = record
and lambdaValue = {
@ -50,17 +51,18 @@ let rec toString = aValue =>
}
| EvBool(aBool) => Js.String.make(aBool)
| EvCall(fName) => `:${fName}`
| EvDate(date) => DateTime.Date.toString(date)
| EvDeclaration(d) => Declaration.toString(d, r => toString(EvLambda(r)))
| EvDistribution(dist) => GenericDist.toString(dist)
| EvLambda(lambdaValue) => `lambda(${Js.Array2.toString(lambdaValue.parameters)}=>internal code)`
| EvModule(m) => `@${m->toStringRecord}`
| EvNumber(aNumber) => Js.String.make(aNumber)
| EvRecord(aRecord) => aRecord->toStringRecord
| EvString(aString) => `'${aString}'`
| EvSymbol(aString) => `:${aString}`
| EvRecord(aRecord) => aRecord->toStringRecord
| 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)))
| EvType(t) => `type${t->toStringRecord}`
| EvTypeIdentifier(id) => `#${id}`
| EvModule(m) => `@${m->toStringRecord}`
}
and toStringRecord = aRecord => {
let pairs =
@ -88,72 +90,3 @@ type environment = DistributionOperation.env
@genType
let defaultEnvironment: environment = DistributionOperation.defaultEnv
type expressionValueType =
| EvtArray
| EvtArrayString
| EvtBool
| EvtCall
| EvtDistribution
| EvtLambda
| EvtNumber
| EvtRecord
| EvtString
| EvtSymbol
| EvtDate
| EvtTimeDuration
| EvtDeclaration
| EvtTypeIdentifier
| EvtModule
type functionCallSignature = CallSignature(string, array<expressionValueType>)
type functionDefinitionSignature =
FunctionDefinitionSignature(functionCallSignature, expressionValueType)
let valueToValueType = value =>
switch value {
| EvArray(_) => EvtArray
| EvArrayString(_) => EvtArrayString
| EvBool(_) => EvtBool
| EvCall(_) => EvtCall
| EvDistribution(_) => EvtDistribution
| EvLambda(_) => EvtLambda
| EvNumber(_) => EvtNumber
| EvRecord(_) => EvtRecord
| EvString(_) => EvtString
| EvSymbol(_) => EvtSymbol
| EvDate(_) => EvtDate
| EvTimeDuration(_) => EvtTimeDuration
| EvDeclaration(_) => EvtDeclaration
| EvTypeIdentifier(_) => EvtTypeIdentifier
| EvModule(_) => EvtModule
}
let functionCallToCallSignature = (functionCall: functionCall): functionCallSignature => {
let (fn, args) = functionCall
CallSignature(fn, args->Js.Array2.map(valueToValueType))
}
let valueTypeToString = (valueType: expressionValueType): string =>
switch valueType {
| EvtArray => `Array`
| EvtArrayString => `ArrayString`
| EvtBool => `Bool`
| EvtCall => `Call`
| EvtDistribution => `Distribution`
| EvtLambda => `Lambda`
| EvtNumber => `Number`
| EvtRecord => `Record`
| EvtString => `String`
| EvtSymbol => `Symbol`
| EvtDate => `Date`
| EvtTimeDuration => `Duration`
| EvtDeclaration => `Declaration`
| EvtTypeIdentifier => `TypeIdentifier`
| EvtModule => `Module`
}
let functionCallSignatureToString = (functionCallSignature: functionCallSignature): string => {
let CallSignature(fn, args) = functionCallSignature
`${fn}(${args->Js.Array2.map(valueTypeToString)->Js.Array2.toString})`
}

View File

@ -7,8 +7,8 @@ type environment = ExternalExpressionValue.environment
let defaultEnvironment = ExternalExpressionValue.defaultEnvironment
type rec t =
| IEvArray(array<t>) // FIXME: Convert
| IEvArrayString(array<string>) // FIXME: Convert
| IEvArray(array<t>) // FIXME: Convert to MapInt
| IEvArrayString(array<string>)
| IEvBool(bool)
| IEvCall(string) // External function call
| IEvDate(Js.Date.t)
@ -21,6 +21,7 @@ type rec t =
| IEvString(string)
| IEvSymbol(string)
| IEvTimeDuration(float)
| IEvType(map)
| IEvTypeIdentifier(string)
and map = Belt.Map.String.t<t>
and nameSpace = NameSpace(Belt.Map.String.t<t>)
@ -56,6 +57,7 @@ let rec toString = aValue =>
| IEvRecord(aMap) => aMap->toStringMap
| IEvString(aString) => `'${aString}'`
| IEvSymbol(aString) => `:${aString}`
| IEvType(aMap) => aMap->toStringMap
| IEvTimeDuration(t) => DateTime.Duration.toString(t)
| IEvTypeIdentifier(id) => `#${id}`
}
@ -78,17 +80,18 @@ let toStringWithType = aValue =>
| IEvArrayString(_) => `ArrayString::${toString(aValue)}`
| IEvBool(_) => `Bool::${toString(aValue)}`
| IEvCall(_) => `Call::${toString(aValue)}`
| IEvDate(_) => `Date::${toString(aValue)}`
| IEvDeclaration(_) => `Declaration::${toString(aValue)}`
| IEvDistribution(_) => `Distribution::${toString(aValue)}`
| IEvLambda(_) => `Lambda::${toString(aValue)}`
| IEvModule(_) => `Module::${toString(aValue)}`
| IEvNumber(_) => `Number::${toString(aValue)}`
| IEvRecord(_) => `Record::${toString(aValue)}`
| IEvString(_) => `String::${toString(aValue)}`
| IEvSymbol(_) => `Symbol::${toString(aValue)}`
| IEvDate(_) => `Date::${toString(aValue)}`
| IEvTimeDuration(_) => `Date::${toString(aValue)}`
| IEvDeclaration(_) => `Declaration::${toString(aValue)}`
| IEvType(_) => `Type::${toString(aValue)}`
| IEvTypeIdentifier(_) => `TypeIdentifier::${toString(aValue)}`
| IEvModule(_) => `Module::${toString(aValue)}`
}
let argsToString = (args: array<t>): string => {
@ -120,17 +123,18 @@ type internalExpressionValueType =
| EvtArrayString
| EvtBool
| EvtCall
| EvtDate
| EvtDeclaration
| EvtDistribution
| EvtLambda
| EvtModule
| EvtNumber
| EvtRecord
| EvtString
| EvtSymbol
| EvtDate
| EvtTimeDuration
| EvtDeclaration
| EvtType
| EvtTypeIdentifier
| EvtModule
type functionCallSignature = CallSignature(string, array<internalExpressionValueType>)
type functionDefinitionSignature =
@ -142,17 +146,18 @@ let valueToValueType = value =>
| IEvArrayString(_) => EvtArrayString
| IEvBool(_) => EvtBool
| IEvCall(_) => EvtCall
| IEvDate(_) => EvtDate
| IEvDeclaration(_) => EvtDeclaration
| IEvDistribution(_) => EvtDistribution
| IEvLambda(_) => EvtLambda
| IEvModule(_) => EvtModule
| IEvNumber(_) => EvtNumber
| IEvRecord(_) => EvtRecord
| IEvString(_) => EvtString
| IEvSymbol(_) => EvtSymbol
| IEvDate(_) => EvtDate
| IEvTimeDuration(_) => EvtTimeDuration
| IEvDeclaration(_) => EvtDeclaration
| IEvType(_) => EvtType
| IEvTypeIdentifier(_) => EvtTypeIdentifier
| IEvModule(_) => EvtModule
}
let functionCallToCallSignature = (functionCall: functionCall): functionCallSignature => {
@ -166,17 +171,18 @@ let valueTypeToString = (valueType: internalExpressionValueType): string =>
| EvtArrayString => `ArrayString`
| EvtBool => `Bool`
| EvtCall => `Call`
| EvtDate => `Date`
| EvtDeclaration => `Declaration`
| EvtDistribution => `Distribution`
| EvtLambda => `Lambda`
| EvtModule => `Module`
| EvtNumber => `Number`
| EvtRecord => `Record`
| EvtString => `String`
| EvtSymbol => `Symbol`
| EvtDate => `Date`
| EvtTimeDuration => `Duration`
| EvtDeclaration => `Declaration`
| EvtType => `Type`
| EvtTypeIdentifier => `TypeIdentifier`
| EvtModule => `Module`
}
let functionCallSignatureToString = (functionCallSignature: functionCallSignature): string => {
@ -190,6 +196,11 @@ let rec toExternal = (iev: t): ExternalExpressionValue.t => {
| IEvArrayString(v) => EvArrayString(v)
| IEvBool(v) => EvBool(v)
| IEvCall(v) => EvCall(v)
| IEvDeclaration(v) => {
let fn = lambdaValueToExternal(v.fn)
let args = v.args
EvDeclaration({fn: fn, args: args})
}
| IEvDistribution(v) => EvDistribution(v)
| IEvLambda(v) => EvLambda(lambdaValueToExternal(v))
| IEvNumber(v) => EvNumber(v)
@ -198,11 +209,7 @@ let rec toExternal = (iev: t): ExternalExpressionValue.t => {
| IEvSymbol(v) => EvSymbol(v)
| IEvDate(v) => EvDate(v)
| IEvTimeDuration(v) => EvTimeDuration(v)
| IEvDeclaration(v) => {
let fn = lambdaValueToExternal(v.fn)
let args = v.args
EvDeclaration({fn: fn, args: args})
}
| IEvType(v) => v->mapToExternal->EvType
| IEvTypeIdentifier(v) => EvTypeIdentifier(v)
| IEvModule(v) => v->nameSpaceToTypeScriptBindings->EvModule
}
@ -228,21 +235,22 @@ let rec toInternal = (ev: ExternalExpressionValue.t): t => {
| EvArrayString(v) => IEvArrayString(v)
| EvBool(v) => IEvBool(v)
| EvCall(v) => IEvCall(v)
| EvDistribution(v) => IEvDistribution(v)
| EvLambda(v) => IEvLambda(lambdaValueToInternal(v))
| EvNumber(v) => IEvNumber(v)
| EvRecord(v) => v->recordToInternal->IEvRecord
| EvString(v) => IEvString(v)
| EvSymbol(v) => IEvSymbol(v)
| EvDate(v) => IEvDate(v)
| EvTimeDuration(v) => IEvTimeDuration(v)
| EvDeclaration(v) => {
let fn = lambdaValueToInternal(v.fn)
let args = v.args
IEvDeclaration({fn: fn, args: args})
}
| EvTypeIdentifier(v) => IEvTypeIdentifier(v)
| EvDistribution(v) => IEvDistribution(v)
| EvLambda(v) => IEvLambda(lambdaValueToInternal(v))
| EvModule(v) => v->nameSpaceFromTypeScriptBindings->IEvModule
| EvNumber(v) => IEvNumber(v)
| EvRecord(v) => v->recordToInternal->IEvRecord
| EvString(v) => IEvString(v)
| EvSymbol(v) => IEvSymbol(v)
| EvTimeDuration(v) => IEvTimeDuration(v)
| EvType(v) => v->recordToInternal->IEvType
| EvTypeIdentifier(v) => IEvTypeIdentifier(v)
}
}
and recordToInternal = v =>

View File

@ -1,4 +1,4 @@
module Module = Reducer_Category_Module
module Module = Reducer_Module
let internalStdLib = Module.emptyModule->SquiggleLibrary_Math.makeBindings

View File

@ -1,5 +1,5 @@
module Bindings = Reducer_Category_Module
module Module = Reducer_Category_Module
module Bindings = Reducer_Module
module Module = Reducer_Module
let availableNumbers: array<(string, float)> = [
("pi", Js.Math._PI),

View File

@ -138,10 +138,10 @@
"devDependencies": {
"@types/glob": "^7.2.0",
"@types/node": "18.x",
"@types/vscode": "^1.68.0",
"@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
"eslint": "^8.18.0",
"@types/vscode": "^1.69.0",
"@typescript-eslint/eslint-plugin": "^5.30.6",
"@typescript-eslint/parser": "^5.30.6",
"eslint": "^8.19.0",
"glob": "^8.0.3",
"js-yaml": "^4.1.0",
"typescript": "^4.7.4",
@ -149,7 +149,7 @@
},
"dependencies": {
"vscode-languageclient": "^8.0.1",
"vscode-languageserver": "^7.0.0",
"vscode-languageserver": "^8.0.1",
"vscode-languageserver-textdocument": "^1.0.5",
"@quri/squiggle-lang": "^0.2.11"
}

View File

@ -70,12 +70,14 @@ uniform(10, 12);
```
beta: (distribution|number, distribution|number) => distribution
beta: (dict<{mean: distribution|number, stdev: distribution|number}>) => distribution
```
**Examples**
```javascript
beta(20, 25);
beta({ mean: 0.39, stdev: 0.1 });
```
### cauchy

View File

@ -16,7 +16,7 @@ If you take the pointwise mixture of two distributions with very different means
In the following case, the mean of the mixture should be equal to the sum of the means of the parts. These are shown as the first two displayed variables. These variables diverge as the underlying distributions change.
<SquiggleEditor
initialSquiggleString={`dist1 = {value: normal(1,1), weight: 1}
defaultCode={`dist1 = {value: normal(1,1), weight: 1}
dist2 = {value: normal(100000000000,1), weight: 1}
totalWeight = dist1.weight + dist2.weight
distMixture = mixture(dist1.value, dist2.value, [dist1.weight, dist2.weight])
@ -30,7 +30,7 @@ separateMeansCombined = (mean(dist1.value) * (dist1.weight) + mean(dist2.value)
The means of sample set distributions can vary dramatically, especially as the numbers get high.
<SquiggleEditor
initialSquiggleString={`symbolicDist = 5 to 50333333
defaultCode={`symbolicDist = 5 to 50333333
sampleSetDist = toSampleSet(symbolicDist)
[mean(symbolicDist), mean(sampleSetDist), symbolicDist, sampleSetDist]`}
/>

View File

@ -22,22 +22,22 @@ If both values are above zero, a `lognormal` distribution is used. If not, a `no
When <code>5 to 10</code> is entered, both numbers are positive, so it
generates a lognormal distribution with 5th and 95th percentiles at 5 and
10.
<SquiggleEditor initialSquiggleString="5 to 10" />
<SquiggleEditor defaultCode="5 to 10" />
</TabItem>
<TabItem value="ex3" label="to(5,10)">
<code>5 to 10</code> does the same thing as <code>to(5,10)</code>.
<SquiggleEditor initialSquiggleString="to(5,10)" />
<SquiggleEditor defaultCode="to(5,10)" />
</TabItem>
<TabItem value="ex2" label="-5 to 5">
When <code>-5 to 5</code> is entered, there's negative values, so it
generates a normal distribution. This has 5th and 95th percentiles at 5 and
10.
<SquiggleEditor initialSquiggleString="-5 to -3" />
<SquiggleEditor defaultCode="-5 to -3" />
</TabItem>
<TabItem value="ex4" label="1 to 10000">
It's very easy to generate distributions with very long tails. If this
happens, you can click the "log x scale" box to view this using a log scale.
<SquiggleEditor initialSquiggleString="1 to 10000" />
<SquiggleEditor defaultCode="1 to 10000" />
</TabItem>
</Tabs>
@ -76,16 +76,16 @@ The `mixture` mixes combines multiple distributions to create a mixture. You can
<Tabs>
<TabItem value="ex1" label="Simple" default>
<SquiggleEditor initialSquiggleString="mixture(1 to 2, 5 to 8, 9 to 10)" />
<SquiggleEditor defaultCode="mixture(1 to 2, 5 to 8, 9 to 10)" />
</TabItem>
<TabItem value="ex2" label="With Weights">
<SquiggleEditor initialSquiggleString="mixture(1 to 2, 5 to 8, 9 to 10, [0.1, 0.1, 0.8])" />
<SquiggleEditor defaultCode="mixture(1 to 2, 5 to 8, 9 to 10, [0.1, 0.1, 0.8])" />
</TabItem>
<TabItem value="ex3" label="With Continuous and Discrete Inputs">
<SquiggleEditor initialSquiggleString="mixture(1 to 5, 8 to 10, 1, 3, 20)" />
<SquiggleEditor defaultCode="mixture(1 to 5, 8 to 10, 1, 3, 20)" />
</TabItem>
<TabItem value="ex4" label="Array of Distributions Input">
<SquiggleEditor initialSquiggleString="mx([1 to 2, exponential(1)], [1,1])" />
<SquiggleEditor defaultCode="mx([1 to 2, exponential(1)], [1,1])" />
</TabItem>
</Tabs>
@ -111,7 +111,7 @@ The `mixture` mixes combines multiple distributions to create a mixture. You can
In this case, I have a 20% chance of spending 0 time with it. I might estimate my hours with,
</p>
<SquiggleEditor
initialSquiggleString={`hours_the_project_will_take = 5 to 20
defaultCode={`hours_the_project_will_take = 5 to 20
chance_of_doing_anything = 0.8
mx(hours_the_project_will_take, 0, [chance_of_doing_anything, 1 - chance_of_doing_anything])`}
/>
@ -125,7 +125,7 @@ mx(hours_the_project_will_take, 0, [chance_of_doing_anything, 1 - chance_of_doin
very wide, just in case they were dramatically off for some weird reason.
</p>
<SquiggleEditor
initialSquiggleString={`forecast = 3 to 30
defaultCode={`forecast = 3 to 30
chance_completely_wrong = 0.05
forecast_if_completely_wrong = -100 to 200
mx(forecast, forecast_if_completely_wrong, [1-chance_completely_wrong, chance_completely_wrong])`}
@ -141,10 +141,10 @@ Creates a [normal distribution](https://en.wikipedia.org/wiki/Normal_distributio
<Tabs>
<TabItem value="ex1" label="normal(5,1)" default>
<SquiggleEditor initialSquiggleString="normal(5, 1)" />
<SquiggleEditor defaultCode="normal(5, 1)" />
</TabItem>
<TabItem value="ex2" label="normal(100000000000, 100000000000)">
<SquiggleEditor initialSquiggleString="normal(100000000000, 100000000000)" />
<SquiggleEditor defaultCode="normal(100000000000, 100000000000)" />
</TabItem>
</Tabs>
@ -165,7 +165,7 @@ Creates a [log-normal distribution](https://en.wikipedia.org/wiki/Log-normal_dis
you take the log of our lognormal distribution. They can be difficult to directly reason about.
Because of this complexity, we recommend typically using the <a href="#to">to</a> syntax instead of estimating `mu` and `sigma` directly.
<SquiggleEditor initialSquiggleString="lognormal(0, 0.7)" />
<SquiggleEditor defaultCode="lognormal(0, 0.7)" />
### Arguments
@ -185,7 +185,7 @@ Because of this complexity, we recommend typically using the <a href="#to">to</a
are identical:
</p>
<SquiggleEditor
initialSquiggleString={`normalMean = 10
defaultCode={`normalMean = 10
normalStdDev = 2
logOfLognormal = log(lognormal(normalMean, normalStdDev))
[logOfLognormal, normal(normalMean, normalStdDev)]`}
@ -198,7 +198,7 @@ logOfLognormal = log(lognormal(normalMean, normalStdDev))
Creates a [uniform distribution](<https://en.wikipedia.org/wiki/Uniform_distribution_(continuous)>) with the given low and high values.
<SquiggleEditor initialSquiggleString="uniform(3,7)" />
<SquiggleEditor defaultCode="uniform(3,7)" />
### Arguments
@ -236,19 +236,19 @@ with values at 1 and 2. Therefore, this is the same as `mixture(pointMass(1),poi
<Tabs>
<TabItem value="ex1" label="pointMass(3)" default>
<SquiggleEditor initialSquiggleString="pointMass(3)" />
<SquiggleEditor defaultCode="pointMass(3)" />
</TabItem>
<TabItem value="ex3" label="mixture(1,3,5)">
<SquiggleEditor initialSquiggleString="mixture(1,3,5)" />
<SquiggleEditor defaultCode="mixture(1,3,5)" />
</TabItem>
<TabItem value="ex2" label="normal(5,2) * 6">
<SquiggleEditor initialSquiggleString="normal(5,2) * 6" />
<SquiggleEditor defaultCode="normal(5,2) * 6" />
</TabItem>
<TabItem value="ex4" label="dotAdd(normal(5,2), 6)">
<SquiggleEditor initialSquiggleString="dotAdd(normal(5,2), 6)" />
<SquiggleEditor defaultCode="dotAdd(normal(5,2), 6)" />
</TabItem>
<TabItem value="ex5" label="dotMultiply(normal(5,2), 6)">
<SquiggleEditor initialSquiggleString="dotMultiply(normal(5,2), 6)" />
<SquiggleEditor defaultCode="dotMultiply(normal(5,2), 6)" />
</TabItem>
</Tabs>
@ -259,24 +259,31 @@ with values at 1 and 2. Therefore, this is the same as `mixture(pointMass(1),poi
## Beta
`beta(alpha:number, beta:number)`
`beta({mean: number, stdev: number})`
Creates a [beta distribution](https://en.wikipedia.org/wiki/Beta_distribution) with the given `alpha` and `beta` values. For a good summary of the beta distribution, see [this explanation](https://stats.stackexchange.com/a/47782) on Stack Overflow.
<Tabs>
<TabItem value="ex1" label="beta(10, 20)" default>
<SquiggleEditor initialSquiggleString="beta(10,20)" />
<SquiggleEditor defaultCode="beta(10,20)" />
</TabItem>
<TabItem value="ex2" label="beta(1000, 1000)">
<SquiggleEditor initialSquiggleString="beta(1000, 2000)" />
<SquiggleEditor defaultCode="beta(1000, 2000)" />
</TabItem>
<TabItem value="ex3" label="beta(1, 10)">
<SquiggleEditor initialSquiggleString="beta(1, 10)" />
<SquiggleEditor defaultCode="beta(1, 10)" />
</TabItem>
<TabItem value="ex4" label="beta(10, 1)">
<SquiggleEditor initialSquiggleString="beta(10, 1)" />
<SquiggleEditor defaultCode="beta(10, 1)" />
</TabItem>
<TabItem value="ex5" label="beta(0.8, 0.8)">
<SquiggleEditor initialSquiggleString="beta(0.8, 0.8)" />
<SquiggleEditor defaultCode="beta(0.8, 0.8)" />
</TabItem>
<TabItem
value="from mean and standard deviation"
label="beta({mean: 0.39, stdev: 0.1})"
>
<SquiggleEditor initialSquiggleString="beta({mean: 0.39, stdev: 0.1})" />
</TabItem>
</Tabs>
@ -295,16 +302,16 @@ Creates a [beta distribution](https://en.wikipedia.org/wiki/Beta_distribution) w
<summary>Examples</summary>
<Tabs>
<TabItem value="ex1" label="beta(0.3, 0.3)" default>
<SquiggleEditor initialSquiggleString="beta(0.3, 0.3)" />
<SquiggleEditor defaultCode="beta(0.3, 0.3)" />
</TabItem>
<TabItem value="ex2" label="beta(0.5, 0.5)">
<SquiggleEditor initialSquiggleString="beta(0.5, 0.5)" />
<SquiggleEditor defaultCode="beta(0.5, 0.5)" />
</TabItem>
<TabItem value="ex3" label="beta(0.8, 0.8)">
<SquiggleEditor initialSquiggleString="beta(.8,.8)" />
<SquiggleEditor defaultCode="beta(.8,.8)" />
</TabItem>
<TabItem value="ex4" label="beta(0.9, 0.9)">
<SquiggleEditor initialSquiggleString="beta(.9,.9)" />
<SquiggleEditor defaultCode="beta(.9,.9)" />
</TabItem>
</Tabs>
</details>
@ -316,7 +323,7 @@ Creates a [beta distribution](https://en.wikipedia.org/wiki/Beta_distribution) w
Creates an [exponential distribution](https://en.wikipedia.org/wiki/Exponential_distribution) with the given rate.
<SquiggleEditor initialSquiggleString="exponential(4)" />
<SquiggleEditor defaultCode="exponential(4)" />
### Arguments
@ -334,7 +341,7 @@ Creates a [triangular distribution](https://en.wikipedia.org/wiki/Triangular_dis
- `mode`: Number greater than `low`
- `high`: Number greater than `mode`
<SquiggleEditor initialSquiggleString="triangular(1, 2, 4)" />
<SquiggleEditor defaultCode="triangular(1, 2, 4)" />
## FromSamples
@ -342,7 +349,7 @@ Creates a [triangular distribution](https://en.wikipedia.org/wiki/Triangular_dis
Creates a sample set distribution using an array of samples.
<SquiggleEditor initialSquiggleString="fromSamples([1,2,3,4,6,5,5,5])" />
<SquiggleEditor defaultCode="fromSamples([1,2,3,4,6,5,5,5])" />
### Arguments

View File

@ -16,7 +16,7 @@ the value of one random sample chosen from the first distribution and the value
chosen from the second distribution.
<SquiggleEditor
initialSquiggleString={`dist1 = 1 to 10
defaultCode={`dist1 = 1 to 10
dist2 = triangular(1,2,3)
dist1 + dist2`}
/>
@ -28,7 +28,7 @@ the distribution of the value of one random sample chosen from the first distrib
the value of one random sample chosen from the second distribution.
<SquiggleEditor
initialSquiggleString={`dist1 = 1 to 10
defaultCode={`dist1 = 1 to 10
dist2 = triangular(1,2,3)
dist1 - dist2`}
/>
@ -40,14 +40,14 @@ the value of one random sample chosen from the first distribution times the valu
chosen from the second distribution.
<SquiggleEditor
initialSquiggleString={`dist1 = 1 to 10
defaultCode={`dist1 = 1 to 10
dist2 = triangular(1,2,3)
dist1 * dist2`}
/>
We also provide concatenation of two distributions as a syntax sugar for `*`
<SquiggleEditor initialSquiggleString="(0.1 to 1) triangular(1,2,3)" />
<SquiggleEditor defaultCode="(0.1 to 1) triangular(1,2,3)" />
### Division
@ -58,7 +58,7 @@ chosen from the second distribution. If the second distribution has some values
tends to be particularly unstable.
<SquiggleEditor
initialSquiggleString={`dist1 = 1 to 10
defaultCode={`dist1 = 1 to 10
dist2 = triangular(1,2,3)
dist1 / dist2`}
/>
@ -69,12 +69,12 @@ A projection over a contracted x-axis. The exponentiation operation represents t
the exponentiation of the value of one random sample chosen from the first distribution to the power of
the value one random sample chosen from the second distribution.
<SquiggleEditor initialSquiggleString={`(0.1 to 1) ^ beta(2, 3)`} />
<SquiggleEditor defaultCode={`(0.1 to 1) ^ beta(2, 3)`} />
### Taking the base `e` exponential
<SquiggleEditor
initialSquiggleString={`dist = triangular(1,2,3)
defaultCode={`dist = triangular(1,2,3)
exp(dist)`}
/>
@ -83,19 +83,19 @@ exp(dist)`}
A projection over a stretched x-axis.
<SquiggleEditor
initialSquiggleString={`dist = triangular(1,2,3)
defaultCode={`dist = triangular(1,2,3)
log(dist)`}
/>
<SquiggleEditor
initialSquiggleString={`dist = beta(1,2)
defaultCode={`dist = beta(1,2)
log10(dist)`}
/>
Base `x`
<SquiggleEditor
initialSquiggleString={`x = 2
defaultCode={`x = 2
dist = beta(2,3)
log(dist, x)`}
/>
@ -114,7 +114,7 @@ For every point on the x-axis, operate the corresponding points in the y axis of
TODO: this isn't in the new interpreter/parser yet.
<SquiggleEditor
initialSquiggleString={`dist1 = 1 to 10
defaultCode={`dist1 = 1 to 10
dist2 = triangular(1,2,3)
dist1 .+ dist2`}
/>
@ -124,7 +124,7 @@ dist1 .+ dist2`}
TODO: this isn't in the new interpreter/parser yet.
<SquiggleEditor
initialSquiggleString={`dist1 = 1 to 10
defaultCode={`dist1 = 1 to 10
dist2 = triangular(1,2,3)
dist1 .- dist2`}
/>
@ -132,7 +132,7 @@ dist1 .- dist2`}
### Pointwise multiplication
<SquiggleEditor
initialSquiggleString={`dist1 = 1 to 10
defaultCode={`dist1 = 1 to 10
dist2 = triangular(1,2,3)
dist1 .* dist2`}
/>
@ -140,7 +140,7 @@ dist1 .* dist2`}
### Pointwise division
<SquiggleEditor
initialSquiggleString={`dist1 = uniform(0,20)
defaultCode={`dist1 = uniform(0,20)
dist2 = normal(10,8)
dist1 ./ dist2`}
/>
@ -148,7 +148,7 @@ dist1 ./ dist2`}
### Pointwise exponentiation
<SquiggleEditor
initialSquiggleString={`dist1 = 1 to 10
defaultCode={`dist1 = 1 to 10
dist2 = triangular(1,2,3)
dist1 .^ dist2`}
/>
@ -160,7 +160,7 @@ dist1 .^ dist2`}
The `pdf(dist, x)` function returns the density of a distribution at the
given point x.
<SquiggleEditor initialSquiggleString="pdf(normal(0,1),0)" />
<SquiggleEditor defaultCode="pdf(normal(0,1),0)" />
#### Validity
@ -172,7 +172,7 @@ given point x.
The `cdf(dist, x)` gives the cumulative probability of the distribution
or all values lower than x. It is the inverse of `quantile`.
<SquiggleEditor initialSquiggleString="cdf(normal(0,1),0)" />
<SquiggleEditor defaultCode="cdf(normal(0,1),0)" />
#### Validity
@ -185,7 +185,7 @@ The `quantile(dist, prob)` gives the value x or which the probability for all va
lower than x is equal to prob. It is the inverse of `cdf`. In the literature, it
is also known as the quantiles function.
<SquiggleEditor initialSquiggleString="quantile(normal(0,1),0.5)" />
<SquiggleEditor defaultCode="quantile(normal(0,1),0.5)" />
#### Validity
@ -196,29 +196,29 @@ is also known as the quantiles function.
The `mean(distribution)` function gives the mean (expected value) of a distribution.
<SquiggleEditor initialSquiggleString="mean(normal(5, 10))" />
<SquiggleEditor defaultCode="mean(normal(5, 10))" />
### Sampling a distribution
The `sample(distribution)` samples a given distribution.
<SquiggleEditor initialSquiggleString="sample(normal(0, 10))" />
<SquiggleEditor defaultCode="sample(normal(0, 10))" />
## Converting between distribution formats
Recall the [three formats of distributions](https://develop--squiggle-documentation.netlify.app/docs/Discussions/Three-Types-Of-Distributions). We can force any distribution into `SampleSet` format
<SquiggleEditor initialSquiggleString="toSampleSet(normal(5, 10))" />
<SquiggleEditor defaultCode="toSampleSet(normal(5, 10))" />
Or `PointSet` format
<SquiggleEditor initialSquiggleString="toPointSet(normal(5, 10))" />
<SquiggleEditor defaultCode="toPointSet(normal(5, 10))" />
### `toSampleSet` has two signatures
Above, we saw the unary `toSampleSet`, which uses an internal hardcoded number of samples. If you'd like to provide the number of samples, it has a binary signature as well (floored)
<SquiggleEditor initialSquiggleString="[toSampleSet(0.1 to 1, 100.1), toSampleSet(0.1 to 1, 5000), toSampleSet(0.1 to 1, 20000)]" />
<SquiggleEditor defaultCode="[toSampleSet(0.1 to 1, 100.1), toSampleSet(0.1 to 1, 5000), toSampleSet(0.1 to 1, 20000)]" />
#### Validity
@ -230,13 +230,13 @@ Some distribution operations (like horizontal shift) return an unnormalized dist
We provide a `normalize` function
<SquiggleEditor initialSquiggleString="normalize((0.1 to 1) + triangular(0.1, 1, 10))" />
<SquiggleEditor defaultCode="normalize((0.1 to 1) + triangular(0.1, 1, 10))" />
#### Validity - Input to `normalize` must be a dist
We provide a predicate `isNormalized`, for when we have simple control flow
<SquiggleEditor initialSquiggleString="isNormalized((0.1 to 1) * triangular(0.1, 1, 10))" />
<SquiggleEditor defaultCode="isNormalized((0.1 to 1) * triangular(0.1, 1, 10))" />
#### Validity
@ -246,7 +246,7 @@ We provide a predicate `isNormalized`, for when we have simple control flow
You may like to debug by right clicking your browser and using the _inspect_ functionality on the webpage, and viewing the _console_ tab. Then, wrap your squiggle output with `inspect` to log an internal representation.
<SquiggleEditor initialSquiggleString="inspect(toSampleSet(0.1 to 1, 100))" />
<SquiggleEditor defaultCode="inspect(toSampleSet(0.1 to 1, 100))" />
Save for a logging side effect, `inspect` does nothing to input and returns it.
@ -254,12 +254,12 @@ Save for a logging side effect, `inspect` does nothing to input and returns it.
You can cut off from the left
<SquiggleEditor initialSquiggleString="truncateLeft(0.1 to 1, 0.5)" />
<SquiggleEditor defaultCode="truncateLeft(0.1 to 1, 0.5)" />
You can cut off from the right
<SquiggleEditor initialSquiggleString="truncateRight(0.1 to 1, 0.5)" />
<SquiggleEditor defaultCode="truncateRight(0.1 to 1, 0.5)" />
You can cut off from both sides
<SquiggleEditor initialSquiggleString="truncate(0.1 to 1, 0.5, 1.5)" />
<SquiggleEditor defaultCode="truncate(0.1 to 1, 0.5, 1.5)" />

View File

@ -9,22 +9,22 @@ import { SquiggleEditor } from "../../src/components/SquiggleEditor";
### Distributions
<SquiggleEditor initialSquiggleString={`mixture(1 to 2, 3, [0.3, 0.7])`} />
<SquiggleEditor defaultCode={`mixture(1 to 2, 3, [0.3, 0.7])`} />
### Numbers
<SquiggleEditor initialSquiggleString="4.32" />
<SquiggleEditor defaultCode="4.32" />
### Arrays
<SquiggleEditor
initialSquiggleString={`[beta(1,10), 4, isNormalized(toSampleSet(1 to 2))]`}
defaultCode={`[beta(1,10), 4, isNormalized(toSampleSet(1 to 2))]`}
/>
### Records
<SquiggleEditor
initialSquiggleString={`d = {dist: triangular(0, 1, 2), weight: 0.25}
defaultCode={`d = {dist: triangular(0, 1, 2), weight: 0.25}
d.dist`}
/>
@ -33,7 +33,7 @@ d.dist`}
A statement assigns expressions to names. It looks like `<symbol> = <expression>`
<SquiggleEditor
initialSquiggleString={`value_of_work = 10 to 70
defaultCode={`value_of_work = 10 to 70
5 + value_of_work / 75`}
/>
@ -42,7 +42,7 @@ A statement assigns expressions to names. It looks like `<symbol> = <expression>
We can define functions
<SquiggleEditor
initialSquiggleString={`ozzie_estimate(t) = lognormal(t^(1.1), 0.5)
defaultCode={`ozzie_estimate(t) = lognormal(t^(1.1), 0.5)
nuno_estimate(t, m) = mixture(normal(-5, 1), lognormal(m, t / 1.25))
ozzie_estimate(1) * nuno_estimate(1, 1)`}
/>

View File

@ -10,7 +10,7 @@ Squiggle is an _estimation language_, and a syntax for _calculating and expressi
- [Gallery](./Discussions/Gallery)
- [Squiggle playground](/playground)
- [Language basics](./Guides/Language)
- [Squiggle functions source of truth](./docs/Guides/Functions)
- [Squiggle functions source of truth](./Guides/Functions)
- [Known bugs](./Discussions/Bugs)
- [Original lesswrong sequence](https://www.lesswrong.com/s/rDe8QE5NvXcZYzgZ3)
- [Author your squiggle models as Observable notebooks](https://observablehq.com/@hazelfire/squiggle)

View File

@ -12,14 +12,14 @@
"format": "prettier --write ."
},
"dependencies": {
"@docusaurus/core": "2.0.0-beta.21",
"@docusaurus/preset-classic": "2.0.0-beta.21",
"@docusaurus/core": "2.0.0-beta.22",
"@docusaurus/preset-classic": "2.0.0-beta.22",
"@quri/squiggle-components": "^0.2.20",
"base64-js": "^1.5.1",
"clsx": "^1.1.1",
"clsx": "^1.2.1",
"hast-util-is-element": "2.1.2",
"pako": "^2.0.4",
"prism-react-renderer": "^1.3.3",
"prism-react-renderer": "^1.3.5",
"react": "^18.1.0",
"react-dom": "^18.2.0",
"rehype-katex": "^5",

View File

@ -36,11 +36,16 @@ function setHashData(data) {
}
export default function PlaygroundPage() {
const hashData = getHashData();
if (hashData.initialSquiggleString) {
hashData.defaultCode = String(hashData.initialSquiggleString);
delete hashData.initialSquiggleString;
}
const playgroundProps = {
initialSquiggleString: "normal(0,1)",
defaultCode: "normal(0,1)",
height: 700,
showTypes: true,
...getHashData(),
...hashData,
onCodeChange: (code) => setHashData({ initialSquiggleString: code }),
onSettingsChange: (settings) => {
const { showTypes, showControls, showSummary, showEditor } = settings;

2200
yarn.lock

File diff suppressed because it is too large Load Diff