= ({
+  initialSquiggleString = "",
+  onChange,
+  bindings,
+}: SquigglePartialProps) => {
+  let [expression, setExpression] = React.useState(initialSquiggleString);
+  let squiggleResult = runPartial(expression, bindings);
+  if (squiggleResult.tag == "Ok") {
+    if (onChange) onChange(squiggleResult.value);
+  }
+  return (
+    
+      
+          {errorValueToString(squiggleResult.value)}
+         
+      ) : (
+        <>>
+      )}
+    
+  );
+};
+
+export function renderSquigglePartialToDom(props: SquigglePartialProps) {
+  let parent = document.createElement("div");
+  ReactDOM.render(
+     {
+        // @ts-ignore
+        parent.value = bindings;
+
+        parent.dispatchEvent(new CustomEvent("input"));
+        if (props.onChange) props.onChange(bindings);
+      }}
+    />,
+    parent
+  );
+  return parent;
+}
diff --git a/packages/components/src/components/SquigglePlayground.tsx b/packages/components/src/components/SquigglePlayground.tsx
index 93e8e9c6..a8ad84d5 100644
--- a/packages/components/src/components/SquigglePlayground.tsx
+++ b/packages/components/src/components/SquigglePlayground.tsx
@@ -1,11 +1,9 @@
 import _ from "lodash";
-import React, { FC, useState } from "react";
+import React, { FC, ReactElement, useState } from "react";
 import ReactDOM from "react-dom";
 import { SquiggleChart } from "./SquiggleChart";
 import CodeEditor from "./CodeEditor";
-import { Form, Input, Row, Col } from "antd";
 import styled from "styled-components";
-import "antd/dist/antd.css";
 
 interface FieldFloatProps {
   label: string;
@@ -14,10 +12,19 @@ interface FieldFloatProps {
   onChange: (value: number) => void;
 }
 
+const Input = styled.input``;
+
+const FormItem = (props: { label: string; children: ReactElement }) => (
+  
+    {props.label} 
+    {props.children}
+  
+);
+
 function FieldFloat(Props: FieldFloatProps) {
   let [contents, setContents] = useState(Props.value + "");
   return (
-    
+    
         
+    
   );
 }
 
@@ -65,6 +72,12 @@ const Display = styled.div`
   max-height: ${(props) => props.maxHeight}px;
 `;
 
+const Row = styled.div`
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+`;
+const Col = styled.div``;
+
 let SquigglePlayground: FC = ({
   initialSquiggleString = "",
   height = 300,
@@ -79,7 +92,7 @@ let SquigglePlayground: FC = ({
   return (
     
       
-         = ({
             height={height - 3}
           />
         
-        
              {
-  makeTest(
-    "splits (1)",
-    SampleSetDist_ToPointSet.Internals.T.splitContinuousAndDiscrete([1.432, 1.33455, 2.0]),
-    ([1.432, 1.33455, 2.0], E.FloatFloatMap.empty()),
-  )
-  makeTest(
-    "splits (2)",
-    SampleSetDist_ToPointSet.Internals.T.splitContinuousAndDiscrete([
-      1.432,
-      1.33455,
-      2.0,
-      2.0,
-      2.0,
-      2.0,
-    ]) |> (((c, disc)) => (c, disc |> E.FloatFloatMap.toArray)),
-    ([1.432, 1.33455], [(2.0, 4.0)]),
-  )
-
-  let makeDuplicatedArray = count => {
-    let arr = Belt.Array.range(1, count) |> E.A.fmap(float_of_int)
-    let sorted = arr |> Belt.SortArray.stableSortBy(_, compare)
-    E.A.concatMany([sorted, sorted, sorted, sorted]) |> Belt.SortArray.stableSortBy(_, compare)
-  }
-
-  let (_, discrete1) = SampleSetDist_ToPointSet.Internals.T.splitContinuousAndDiscrete(
-    makeDuplicatedArray(10),
-  )
-  let toArr1 = discrete1 |> E.FloatFloatMap.toArray
-  makeTest("splitMedium at count=10", toArr1 |> Belt.Array.length, 10)
-
-  let (_c, discrete2) = SampleSetDist_ToPointSet.Internals.T.splitContinuousAndDiscrete(
-    makeDuplicatedArray(500),
-  )
-  let toArr2 = discrete2 |> E.FloatFloatMap.toArray
-  makeTest("splitMedium at count=500", toArr2 |> Belt.Array.length, 500)
-  // makeTest("foo", [] |> Belt.Array.length, 500)
-})
diff --git a/packages/squiggle-lang/__tests__/E/splitContinuousAndDiscrete_test.res b/packages/squiggle-lang/__tests__/E/splitContinuousAndDiscrete_test.res
new file mode 100644
index 00000000..a52227ee
--- /dev/null
+++ b/packages/squiggle-lang/__tests__/E/splitContinuousAndDiscrete_test.res
@@ -0,0 +1,48 @@
+open Jest
+open TestHelpers
+
+let prepareInputs = (ar, minWeight) =>
+  E.A.Floats.Sorted.splitContinuousAndDiscreteForMinWeight(ar, ~minDiscreteWeight=minWeight) |> (
+    ((c, disc)) => (c, disc |> E.FloatFloatMap.toArray)
+  )
+
+describe("Continuous and discrete splits", () => {
+  makeTest(
+    "is empty, with no common elements",
+    prepareInputs([1.432, 1.33455, 2.0], 2),
+    ([1.33455, 1.432, 2.0], []),
+  )
+
+  makeTest(
+    "only stores 3.5 as discrete when minWeight is 3",
+    prepareInputs([1.432, 1.33455, 2.0, 2.0, 3.5, 3.5, 3.5], 3),
+    ([1.33455, 1.432, 2.0, 2.0], [(3.5, 3.0)]),
+  )
+
+  makeTest(
+    "doesn't store 3.5 as discrete when minWeight is 5",
+    prepareInputs([1.432, 1.33455, 2.0, 2.0, 3.5, 3.5, 3.5], 5),
+    ([1.33455, 1.432, 2.0, 2.0, 3.5, 3.5, 3.5], []),
+  )
+
+  let makeDuplicatedArray = count => {
+    let arr = Belt.Array.range(1, count) |> E.A.fmap(float_of_int)
+    let sorted = arr |> Belt.SortArray.stableSortBy(_, compare)
+    E.A.concatMany([sorted, sorted, sorted, sorted]) |> Belt.SortArray.stableSortBy(_, compare)
+  }
+
+  let (_, discrete1) = E.A.Floats.Sorted.splitContinuousAndDiscreteForMinWeight(
+    makeDuplicatedArray(10),
+    ~minDiscreteWeight=2,
+  )
+  let toArr1 = discrete1 |> E.FloatFloatMap.toArray
+  makeTest("splitMedium at count=10", toArr1 |> Belt.Array.length, 10)
+
+  let (_c, discrete2) = E.A.Floats.Sorted.splitContinuousAndDiscreteForMinWeight(
+    makeDuplicatedArray(500),
+    ~minDiscreteWeight=2,
+  )
+  let toArr2 = discrete2 |> E.FloatFloatMap.toArray
+  makeTest("splitMedium at count=500", toArr2 |> Belt.Array.length, 500)
+  // makeTest("foo", [] |> Belt.Array.length, 500)
+})
diff --git a/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_Distribution_test.res b/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_Distribution_test.res
index 1ff1c85e..2c0cc3e6 100644
--- a/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_Distribution_test.res
+++ b/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_Distribution_test.res
@@ -92,11 +92,11 @@ describe("eval on distribution functions", () => {
     testEval("log(2, uniform(5,8))", "Ok(Sample Set Distribution)")
     testEval(
       "log(normal(5,2), 3)",
-      "Error(Distribution Math Error: Logarithm of input error: First input must completely greater than 0)",
+      "Error(Distribution Math Error: Logarithm of input error: First input must be completely greater than 0)",
     )
     testEval(
       "log(normal(5,2), normal(10,1))",
-      "Error(Distribution Math Error: Logarithm of input error: First input must completely greater than 0)",
+      "Error(Distribution Math Error: Logarithm of input error: First input must be completely greater than 0)",
     )
     testEval("log(uniform(5,8))", "Ok(Sample Set Distribution)")
     testEval("log10(uniform(5,8))", "Ok(Sample Set Distribution)")
diff --git a/packages/squiggle-lang/__tests__/TS/JS_test.ts b/packages/squiggle-lang/__tests__/TS/JS_test.ts
index 1974dee6..e522eb95 100644
--- a/packages/squiggle-lang/__tests__/TS/JS_test.ts
+++ b/packages/squiggle-lang/__tests__/TS/JS_test.ts
@@ -1,23 +1,5 @@
-import {
-  run,
-  Distribution,
-  resultMap,
-  squiggleExpression,
-  errorValueToString,
-} from "../../src/js/index";
-
-let testRun = (x: string): squiggleExpression => {
-  let result = run(x, { sampleCount: 100, xyPointLength: 100 });
-  expect(result.tag).toEqual("Ok");
-  if (result.tag === "Ok") {
-    return result.value;
-  } else {
-    throw Error(
-      "Expected squiggle expression to evaluate but got error: " +
-        errorValueToString(result.value)
-    );
-  }
-};
+import { Distribution, resultMap } from "../../src/js/index";
+import { testRun, testRunPartial } from "./TestHelpers";
 
 function Ok(x: b) {
   return { tag: "Ok", value: x };
@@ -42,6 +24,50 @@ describe("Log function", () => {
   });
 });
 
+describe("Array", () => {
+  test("nested Array", () => {
+    expect(testRun("[[1]]")).toEqual({
+      tag: "array",
+      value: [
+        {
+          tag: "array",
+          value: [
+            {
+              tag: "number",
+              value: 1,
+            },
+          ],
+        },
+      ],
+    });
+  });
+});
+
+describe("Record", () => {
+  test("Return record", () => {
+    expect(testRun("{a: 1}")).toEqual({
+      tag: "record",
+      value: {
+        a: {
+          tag: "number",
+          value: 1,
+        },
+      },
+    });
+  });
+});
+
+describe("Partials", () => {
+  test("Can pass variables between partials and cells", () => {
+    let bindings = testRunPartial(`x = 5`);
+    let bindings2 = testRunPartial(`y = x + 2`, bindings);
+    expect(testRun(`y + 3`, bindings2)).toEqual({
+      tag: "number",
+      value: 10,
+    });
+  });
+});
+
 describe("Distribution", () => {
   //It's important that sampleCount is less than 9. If it's more, than that will create randomness
   //Also, note, the value should be created using makeSampleSetDist() later on.
diff --git a/packages/squiggle-lang/__tests__/TS/TestHelpers.ts b/packages/squiggle-lang/__tests__/TS/TestHelpers.ts
index 3d4153ef..7d51c98e 100644
--- a/packages/squiggle-lang/__tests__/TS/TestHelpers.ts
+++ b/packages/squiggle-lang/__tests__/TS/TestHelpers.ts
@@ -1,14 +1,16 @@
 import {
   run,
-  // Distribution,
+  runPartial,
+  bindings,
   squiggleExpression,
   errorValueToString,
-  // errorValue,
-  // result,
 } from "../../src/js/index";
 
-export function testRun(x: string): squiggleExpression {
-  let squiggleResult = run(x, { sampleCount: 1000, xyPointLength: 100 });
+export function testRun(x: string, bindings = {}): squiggleExpression {
+  let squiggleResult = run(x, bindings, {
+    sampleCount: 1000,
+    xyPointLength: 100,
+  });
   // return squiggleResult.value
   if (squiggleResult.tag === "Ok") {
     return squiggleResult.value;
@@ -21,6 +23,22 @@ export function testRun(x: string): squiggleExpression {
   }
 }
 
+export function testRunPartial(x: string, bindings: bindings = {}): bindings {
+  let squiggleResult = runPartial(x, bindings, {
+    sampleCount: 1000,
+    xyPointLength: 100,
+  });
+  if (squiggleResult.tag === "Ok") {
+    return squiggleResult.value;
+  } else {
+    throw new Error(
+      `Expected squiggle expression to evaluate but got error: ${errorValueToString(
+        squiggleResult.value
+      )}`
+    );
+  }
+}
+
 export function failDefault() {
   expect("be reached").toBe("codepath should never");
 }
diff --git a/packages/squiggle-lang/__tests__/XYShape_test.res b/packages/squiggle-lang/__tests__/XYShape_test.res
index 701d82e1..38535020 100644
--- a/packages/squiggle-lang/__tests__/XYShape_test.res
+++ b/packages/squiggle-lang/__tests__/XYShape_test.res
@@ -18,7 +18,26 @@ let pointSetDist3: PointSetTypes.xyShape = {
   ys: [0.2, 0.5, 0.8],
 }
 
+let makeAndGetErrorString = (~xs, ~ys) =>
+  XYShape.T.make(~xs, ~ys)->E.R.getError->E.O2.fmap(XYShape.Error.toString)
+
 describe("XYShapes", () => {
+  describe("Validator", () => {
+    makeTest(
+      "with no errors",
+      makeAndGetErrorString(~xs=[1.0, 4.0, 8.0], ~ys=[0.2, 0.4, 0.8]),
+      None,
+    )
+    makeTest("when empty", makeAndGetErrorString(~xs=[], ~ys=[]), Some("Xs is empty"))
+    makeTest(
+      "when not sorted, different lengths, and not finite",
+      makeAndGetErrorString(~xs=[2.0, 1.0, infinity, 0.0], ~ys=[3.0, Js.Float._NaN]),
+      Some(
+        "Multiple Errors: [Xs is not sorted], [Xs and Ys have different lengths. Xs has length 4 and Ys has length 2], [Xs is not finite. Example value: Infinity], [Ys is not finite. Example value: NaN]",
+      ),
+    )
+  })
+
   describe("logScorePoint", () => {
     makeTest("When identical", XYShape.logScorePoint(30, pointSetDist1, pointSetDist1), Some(0.0))
     makeTest(
@@ -32,16 +51,6 @@ describe("XYShapes", () => {
       Some(210.3721280423322),
     )
   })
-  // describe("transverse", () => {
-  //   makeTest(
-  //     "When very different",
-  //     XYShape.Transversal._transverse(
-  //       (aCurrent, aLast) => aCurrent +. aLast,
-  //       [|1.0, 2.0, 3.0, 4.0|],
-  //     ),
-  //     [|1.0, 3.0, 6.0, 10.0|],
-  //   )
-  // });
   describe("integrateWithTriangles", () =>
     makeTest(
       "integrates correctly",
diff --git a/packages/squiggle-lang/bsconfig.json b/packages/squiggle-lang/bsconfig.json
index 4c27a48a..e98b3822 100644
--- a/packages/squiggle-lang/bsconfig.json
+++ b/packages/squiggle-lang/bsconfig.json
@@ -20,7 +20,7 @@
   ],
   "suffix": ".bs.js",
   "namespace": true,
-  "bs-dependencies": ["@glennsl/rescript-jest", "rationale", "bisect_ppx"],
+  "bs-dependencies": ["@glennsl/rescript-jest", "bisect_ppx"],
   "gentypeconfig": {
     "language": "typescript",
     "module": "commonjs",
diff --git a/packages/squiggle-lang/package.json b/packages/squiggle-lang/package.json
index f9157ada..4d146ef8 100644
--- a/packages/squiggle-lang/package.json
+++ b/packages/squiggle-lang/package.json
@@ -1,13 +1,13 @@
 {
   "name": "@quri/squiggle-lang",
-  "version": "0.2.5",
+  "version": "0.2.7",
   "homepage": "https://squiggle-language.com",
-  "licence": "MIT",
+  "license": "MIT",
   "scripts": {
-    "build": "rescript build -with-deps",
+    "build": "rescript build -with-deps && tsc",
     "bundle": "webpack",
     "start": "rescript build -w -with-deps",
-    "clean": "rescript clean",
+    "clean": "rescript clean && rm -r dist",
     "test:reducer": "jest __tests__/Reducer*/",
     "benchmark": "ts-node benchmark/conversion_tests.ts",
     "test": "jest",
@@ -31,34 +31,28 @@
   ],
   "author": "Quantified Uncertainty Research Institute",
   "license": "MIT",
+  "dependencies": {
+    "rescript": "^9.1.4",
+    "jstat": "^1.9.5",
+    "pdfast": "^0.2.0",
+    "mathjs": "10.5.0"
+  },
   "devDependencies": {
     "bisect_ppx": "^2.7.1",
-    "jstat": "^1.9.5",
     "lodash": "4.17.21",
-    "mathjs": "10.5.0",
-    "pdfast": "^0.2.0",
-    "rationale": "0.2.0",
-    "rescript": "^9.1.4",
     "rescript-fast-check": "^1.1.1",
     "@glennsl/rescript-jest": "^0.9.0",
     "@istanbuljs/nyc-config-typescript": "^1.0.2",
     "@types/jest": "^27.4.0",
     "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
-    "bisect_ppx": "^2.7.1",
-    "chalk": "^4.1.2",
+    "chalk": "^5.0.1",
     "codecov": "3.8.3",
     "fast-check": "2.25.0",
     "gentype": "^4.3.0",
     "jest": "^27.5.1",
-    "jstat": "^1.9.5",
-    "lodash": "4.17.21",
-    "mathjs": "10.5.0",
     "moduleserve": "0.9.1",
     "nyc": "^15.1.0",
-    "pdfast": "^0.2.0",
-    "rationale": "0.2.0",
     "reanalyze": "^2.19.0",
-    "rescript": "^9.1.4",
     "ts-jest": "^27.1.4",
     "ts-loader": "^9.2.8",
     "ts-node": "^10.7.0",
@@ -67,6 +61,6 @@
     "webpack-cli": "^4.9.2"
   },
   "source": "./src/js/index.ts",
-  "main": "./dist/bundle.js",
-  "types": "./dist/js/index.d.ts"
+  "main": "./dist/src/js/index.js",
+  "types": "./dist/src/js/index.d.ts"
 }
diff --git a/packages/squiggle-lang/src/js/index.ts b/packages/squiggle-lang/src/js/index.ts
index bed78695..34318424 100644
--- a/packages/squiggle-lang/src/js/index.ts
+++ b/packages/squiggle-lang/src/js/index.ts
@@ -2,7 +2,8 @@ import * as _ from "lodash";
 import {
   genericDist,
   samplingParams,
-  evaluate,
+  evaluatePartialUsingExternalBindings,
+  externalBindings,
   expressionValue,
   errorValue,
   distributionError,
@@ -11,6 +12,9 @@ import {
   discreteShape,
   distributionErrorToString,
   internalCode,
+  mixedShape,
+  sampleSetDist,
+  symbolicDist,
 } from "../rescript/TypescriptInterface.gen";
 export {
   makeSampleSetDist,
@@ -44,7 +48,7 @@ import {
   Constructors_pointwiseLogarithm,
   Constructors_pointwisePower,
 } from "../rescript/Distributions/DistributionOperation/DistributionOperation.gen";
-export type { samplingParams, errorValue };
+export type { samplingParams, errorValue, externalBindings as bindings };
 
 export let defaultSamplingInputs: samplingParams = {
   sampleCount: 10000,
@@ -92,26 +96,67 @@ export type squiggleExpression =
   | tagged<"distribution", Distribution>
   | tagged<"number", number>
   | tagged<"record", { [key: string]: squiggleExpression }>;
+
 export function run(
   squiggleString: string,
+  bindings?: externalBindings,
   samplingInputs?: samplingParams
 ): result {
+  let b = bindings ? bindings : {};
   let si: samplingParams = samplingInputs
     ? samplingInputs
     : defaultSamplingInputs;
-  let result: result = evaluate(squiggleString);
+
+  let result: result =
+    evaluateUsingExternalBindings(squiggleString, b);
   return resultMap(result, (x) => createTsExport(x, si));
 }
 
+// Run Partial. A partial is a block of code that doesn't return a value
+export function runPartial(
+  squiggleString: string,
+  bindings: externalBindings,
+  _samplingInputs?: samplingParams
+): result {
+  return evaluatePartialUsingExternalBindings(squiggleString, bindings);
+}
+
 function createTsExport(
   x: expressionValue,
   sampEnv: samplingParams
 ): squiggleExpression {
   switch (x.tag) {
     case "EvArray":
+      // genType doesn't convert anything more than 2 layers down into {tag: x, value: x}
+      // format, leaving it as the raw values. This converts the raw values
+      // directly into typescript values.
+      //
+      // The casting here is because genType is about the types of the returned
+      // values, claiming they are fully recursive when that's not actually the
+      // case
       return tag(
         "array",
-        x.value.map((x) => createTsExport(x, sampEnv))
+        x.value.map((arrayItem): squiggleExpression => {
+          switch (arrayItem.tag) {
+            case "EvRecord":
+              return tag(
+                "record",
+                _.mapValues(arrayItem.value, (recordValue: unknown) =>
+                  convertRawToTypescript(recordValue as rescriptExport, sampEnv)
+                )
+              );
+            case "EvArray":
+              let y = arrayItem.value as unknown as rescriptExport[];
+              return tag(
+                "array",
+                y.map((childArrayItem) =>
+                  convertRawToTypescript(childArrayItem, sampEnv)
+                )
+              );
+            default:
+              return createTsExport(arrayItem, sampEnv);
+          }
+        })
       );
     case "EvBool":
       return tag("boolean", x.value);
@@ -124,10 +169,14 @@ function createTsExport(
     case "EvNumber":
       return tag("number", x.value);
     case "EvRecord":
-      return tag(
+      // genType doesn't support records, so we have to do the raw conversion ourself
+      let result: tagged<"record", { [key: string]: squiggleExpression }> = tag(
         "record",
-        _.mapValues(x.value, (x) => createTsExport(x, sampEnv))
+        _.mapValues(x.value, (x: unknown) =>
+          convertRawToTypescript(x as rescriptExport, sampEnv)
+        )
       );
+      return result;
     case "EvString":
       return tag("string", x.value);
     case "EvSymbol":
@@ -135,6 +184,118 @@ function createTsExport(
   }
 }
 
+// Helper functions to convert the rescript representations that genType doesn't
+// cover
+function convertRawToTypescript(
+  result: rescriptExport,
+  sampEnv: samplingParams
+): squiggleExpression {
+  switch (result.TAG) {
+    case 0: // EvArray
+      return tag(
+        "array",
+        result._0.map((x) => convertRawToTypescript(x, sampEnv))
+      );
+    case 1: // EvBool
+      return tag("boolean", result._0);
+    case 2: // EvCall
+      return tag("call", result._0);
+    case 3: // EvDistribution
+      return tag(
+        "distribution",
+        new Distribution(
+          convertRawDistributionToGenericDist(result._0),
+          sampEnv
+        )
+      );
+    case 4: // EvNumber
+      return tag("number", result._0);
+    case 5: // EvRecord
+      return tag(
+        "record",
+        _.mapValues(result._0, (x) => convertRawToTypescript(x, sampEnv))
+      );
+    case 6: // EvString
+      return tag("string", result._0);
+    case 7: // EvSymbol
+      return tag("symbol", result._0);
+  }
+}
+
+function convertRawDistributionToGenericDist(
+  result: rescriptDist
+): genericDist {
+  switch (result.TAG) {
+    case 0: // Point Set Dist
+      switch (result._0.TAG) {
+        case 0: // Mixed
+          return tag("PointSet", tag("Mixed", result._0._0));
+        case 1: // Discrete
+          return tag("PointSet", tag("Discrete", result._0._0));
+        case 2: // Continuous
+          return tag("PointSet", tag("Continuous", result._0._0));
+      }
+    case 1: // Sample Set Dist
+      return tag("SampleSet", result._0);
+    case 2: // Symbolic Dist
+      return tag("Symbolic", result._0);
+  }
+}
+
+// Raw rescript types.
+type rescriptExport =
+  | {
+      TAG: 0; // EvArray
+      _0: rescriptExport[];
+    }
+  | {
+      TAG: 1; // EvBool
+      _0: boolean;
+    }
+  | {
+      TAG: 2; // EvCall
+      _0: string;
+    }
+  | {
+      TAG: 3; // EvDistribution
+      _0: rescriptDist;
+    }
+  | {
+      TAG: 4; // EvNumber
+      _0: number;
+    }
+  | {
+      TAG: 5; // EvRecord
+      _0: { [key: string]: rescriptExport };
+    }
+  | {
+      TAG: 6; // EvString
+      _0: string;
+    }
+  | {
+      TAG: 7; // EvSymbol
+      _0: string;
+    };
+
+type rescriptDist =
+  | { TAG: 0; _0: rescriptPointSetDist }
+  | { TAG: 1; _0: sampleSetDist }
+  | { TAG: 2; _0: symbolicDist };
+
+type rescriptPointSetDist =
+  | {
+      TAG: 0; // Mixed
+      _0: mixedShape;
+    }
+  | {
+      TAG: 1; // Discrete
+      _0: discreteShape;
+    }
+  | {
+      TAG: 2; // ContinuousShape
+      _0: continuousShape;
+    };
+
 export function resultExn(r: result ): a | c {
   return r.value;
 }
diff --git a/packages/squiggle-lang/src/rescript/Distributions/DistributionTypes.res b/packages/squiggle-lang/src/rescript/Distributions/DistributionTypes.res
index e27a138d..93f86798 100644
--- a/packages/squiggle-lang/src/rescript/Distributions/DistributionTypes.res
+++ b/packages/squiggle-lang/src/rescript/Distributions/DistributionTypes.res
@@ -19,6 +19,7 @@ type error =
   | RequestedStrategyInvalidError(string)
   | LogarithmOfDistributionError(string)
   | OtherError(string)
+  | XYShapeError(XYShape.error)
 
 @genType
 module Error = {
@@ -39,6 +40,7 @@ module Error = {
     | PointSetConversionError(err) => SampleSetDist.pointsetConversionErrorToString(err)
     | SparklineError(err) => PointSetTypes.sparklineErrorToString(err)
     | RequestedStrategyInvalidError(err) => `Requested strategy invalid: ${err}`
+    | XYShapeError(err) => `XY Shape Error: ${XYShape.Error.toString(err)}`
     | OtherError(s) => s
     }
 
diff --git a/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.res b/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.res
index 2d8d0fa8..c19bdf7f 100644
--- a/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.res
+++ b/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.res
@@ -6,6 +6,24 @@ type toSampleSetFn = t => result
 type scaleMultiplyFn = (t, float) => result
 type pointwiseAddFn = (t, t) => result
 
+let isPointSet = (t: t) =>
+  switch t {
+  | PointSet(_) => true
+  | _ => false
+  }
+
+let isSampleSetSet = (t: t) =>
+  switch t {
+  | SampleSet(_) => true
+  | _ => false
+  }
+
+let isSymbolic = (t: t) =>
+  switch t {
+  | Symbolic(_) => true
+  | _ => false
+  }
+
 let sampleN = (t: t, n) =>
   switch t {
   | PointSet(r) => PointSetDist.sampleNRendered(n, r)
@@ -150,144 +168,147 @@ let truncate = Truncate.run
    of a new variable that is the result of the operation on A and B.
    For instance, normal(0, 1) + normal(1, 1) -> normal(1, 2).
    In general, this is implemented via convolution.
-
-  TODO: It would be useful to be able to pass in a paramater to get this to run either with convolution or monte carlo.
 */
 module AlgebraicCombination = {
-  let runConvolution = (
-    toPointSet: toPointSetFn,
-    arithmeticOperation: Operation.convolutionOperation,
-    t1: t,
-    t2: t,
-  ) =>
-    E.R.merge(toPointSet(t1), toPointSet(t2))->E.R2.fmap(((a, b)) =>
-      PointSetDist.combineAlgebraically(arithmeticOperation, a, b)
-    )
-
-  let runMonteCarlo = (
-    toSampleSet: toSampleSetFn,
-    arithmeticOperation: Operation.algebraicOperation,
-    t1: t,
-    t2: t,
-  ): result => {
-    let fn = Operation.Algebraic.toFn(arithmeticOperation)
-    E.R.merge(toSampleSet(t1), toSampleSet(t2))
-    ->E.R.bind(((t1, t2)) => {
-      SampleSetDist.map2(~fn, ~t1, ~t2)->E.R2.errMap(x => DistributionTypes.OperationError(x))
-    })
-    ->E.R2.fmap(r => DistributionTypes.SampleSet(r))
-  }
-
-  /*
+  module InputValidator = {
+    /*
      It would be good to also do a check to make sure that probability mass for the second
      operand, at value 1.0, is 0 (or approximately 0). However, we'd ideally want to check 
      that both the probability mass and the probability density are greater than zero.
      Right now we don't yet have a way of getting probability mass, so I'll leave this for later.
  */
-  let getLogarithmInputError = (t1: t, t2: t, ~toPointSetFn: toPointSetFn): option => {
-    let firstOperandIsGreaterThanZero =
-      toFloatOperation(
-        t1,
-        ~toPointSetFn,
-        ~distToFloatOperation=#Cdf(MagicNumbers.Epsilon.ten),
-      ) |> E.R.fmap(r => r > 0.)
-    let secondOperandIsGreaterThanZero =
-      toFloatOperation(
-        t2,
-        ~toPointSetFn,
-        ~distToFloatOperation=#Cdf(MagicNumbers.Epsilon.ten),
-      ) |> E.R.fmap(r => r > 0.)
-    let items = E.A.R.firstErrorOrOpen([
-      firstOperandIsGreaterThanZero,
-      secondOperandIsGreaterThanZero,
-    ])
-    switch items {
-    | Error(r) => Some(r)
-    | Ok([true, _]) =>
-      Some(LogarithmOfDistributionError("First input must completely greater than 0"))
-    | Ok([false, true]) =>
-      Some(LogarithmOfDistributionError("Second input must completely greater than 0"))
-    | Ok([false, false]) => None
-    | Ok(_) => Some(Unreachable)
+    let getLogarithmInputError = (t1: t, t2: t, ~toPointSetFn: toPointSetFn): option => {
+      let firstOperandIsGreaterThanZero =
+        toFloatOperation(
+          t1,
+          ~toPointSetFn,
+          ~distToFloatOperation=#Cdf(MagicNumbers.Epsilon.ten),
+        ) |> E.R.fmap(r => r > 0.)
+      let secondOperandIsGreaterThanZero =
+        toFloatOperation(
+          t2,
+          ~toPointSetFn,
+          ~distToFloatOperation=#Cdf(MagicNumbers.Epsilon.ten),
+        ) |> E.R.fmap(r => r > 0.)
+      let items = E.A.R.firstErrorOrOpen([
+        firstOperandIsGreaterThanZero,
+        secondOperandIsGreaterThanZero,
+      ])
+      switch items {
+      | Error(r) => Some(r)
+      | Ok([true, _]) =>
+        Some(LogarithmOfDistributionError("First input must be completely greater than 0"))
+      | Ok([false, true]) =>
+        Some(LogarithmOfDistributionError("Second input must be completely greater than 0"))
+      | Ok([false, false]) => None
+      | Ok(_) => Some(Unreachable)
+      }
+    }
+
+    let run = (t1: t, t2: t, ~toPointSetFn: toPointSetFn, ~arithmeticOperation): option => {
+      if arithmeticOperation == #Logarithm {
+        getLogarithmInputError(t1, t2, ~toPointSetFn)
+      } else {
+        None
+      }
     }
   }
 
-  let getInvalidOperationError = (
-    t1: t,
-    t2: t,
-    ~toPointSetFn: toPointSetFn,
+  module StrategyCallOnValidatedInputs = {
+    let convolution = (
+      toPointSet: toPointSetFn,
+      arithmeticOperation: Operation.convolutionOperation,
+      t1: t,
+      t2: t,
+    ): result =>
+      E.R.merge(toPointSet(t1), toPointSet(t2))
+      ->E.R2.fmap(((a, b)) => PointSetDist.combineAlgebraically(arithmeticOperation, a, b))
+      ->E.R2.fmap(r => DistributionTypes.PointSet(r))
+
+    let monteCarlo = (
+      toSampleSet: toSampleSetFn,
+      arithmeticOperation: Operation.algebraicOperation,
+      t1: t,
+      t2: t,
+    ): result => {
+      let fn = Operation.Algebraic.toFn(arithmeticOperation)
+      E.R.merge(toSampleSet(t1), toSampleSet(t2))
+      ->E.R.bind(((t1, t2)) => {
+        SampleSetDist.map2(~fn, ~t1, ~t2)->E.R2.errMap(x => DistributionTypes.OperationError(x))
+      })
+      ->E.R2.fmap(r => DistributionTypes.SampleSet(r))
+    }
+
+    let symbolic = (
+      arithmeticOperation: Operation.algebraicOperation,
+      t1: t,
+      t2: t,
+    ): SymbolicDistTypes.analyticalSimplificationResult => {
+      switch (t1, t2) {
+      | (DistributionTypes.Symbolic(d1), DistributionTypes.Symbolic(d2)) =>
+        SymbolicDist.T.tryAnalyticalSimplification(d1, d2, arithmeticOperation)
+      | _ => #NoSolution
+      }
+    }
+  }
+
+  module StrategyChooser = {
+    type specificStrategy = [#AsSymbolic | #AsMonteCarlo | #AsConvolution]
+
+    //I'm (Ozzie) really just guessing here, very little idea what's best
+    let expectedConvolutionCost: t => int = x =>
+      switch x {
+      | Symbolic(#Float(_)) => MagicNumbers.OpCost.floatCost
+      | Symbolic(_) => MagicNumbers.OpCost.symbolicCost
+      | PointSet(Discrete(m)) => m.xyShape->XYShape.T.length
+      | PointSet(Mixed(_)) => MagicNumbers.OpCost.mixedCost
+      | PointSet(Continuous(_)) => MagicNumbers.OpCost.continuousCost
+      | _ => MagicNumbers.OpCost.wildcardCost
+      }
+
+    let hasSampleSetDist = (t1: t, t2: t): bool => isSampleSetSet(t1) || isSampleSetSet(t2)
+
+    let convolutionIsFasterThanMonteCarlo = (t1: t, t2: t): bool =>
+      expectedConvolutionCost(t1) * expectedConvolutionCost(t2) < MagicNumbers.OpCost.monteCarloCost
+
+    let preferConvolutionToMonteCarlo = (t1, t2, arithmeticOperation) => {
+      !hasSampleSetDist(t1, t2) &&
+      Operation.Convolution.canDoAlgebraicOperation(arithmeticOperation) &&
+      convolutionIsFasterThanMonteCarlo(t1, t2)
+    }
+
+    let run = (~t1: t, ~t2: t, ~arithmeticOperation): specificStrategy => {
+      switch StrategyCallOnValidatedInputs.symbolic(arithmeticOperation, t1, t2) {
+      | #AnalyticalSolution(_)
+      | #Error(_) =>
+        #AsSymbolic
+      | #NoSolution =>
+        preferConvolutionToMonteCarlo(t1, t2, arithmeticOperation) ? #AsConvolution : #AsMonteCarlo
+      }
+    }
+  }
+
+  let runStrategyOnValidatedInputs = (
+    ~t1: t,
+    ~t2: t,
     ~arithmeticOperation,
-  ): option => {
-    if arithmeticOperation == #Logarithm {
-      getLogarithmInputError(t1, t2, ~toPointSetFn)
-    } else {
-      None
-    }
-  }
-
-  //I'm (Ozzie) really just guessing here, very little idea what's best
-  let expectedConvolutionCost: t => int = x =>
-    switch x {
-    | Symbolic(#Float(_)) => MagicNumbers.OpCost.floatCost
-    | Symbolic(_) => MagicNumbers.OpCost.symbolicCost
-    | PointSet(Discrete(m)) => m.xyShape->XYShape.T.length
-    | PointSet(Mixed(_)) => MagicNumbers.OpCost.mixedCost
-    | PointSet(Continuous(_)) => MagicNumbers.OpCost.continuousCost
-    | _ => MagicNumbers.OpCost.wildcardCost
-    }
-
-  type calculationStrategy = MonteCarloStrat | ConvolutionStrat(Operation.convolutionOperation)
-
-  let chooseConvolutionOrMonteCarloDefault = (
-    op: Operation.algebraicOperation,
-    t2: t,
-    t1: t,
-  ): calculationStrategy =>
-    switch op {
-    | #Divide
-    | #Power
-    | #Logarithm =>
-      MonteCarloStrat
-    | (#Add | #Subtract | #Multiply) as convOp =>
-      expectedConvolutionCost(t1) * expectedConvolutionCost(t2) > MagicNumbers.OpCost.monteCarloCost
-        ? MonteCarloStrat
-        : ConvolutionStrat(convOp)
-    }
-
-  let tryAnalyticalSimplification = (
-    arithmeticOperation: Operation.algebraicOperation,
-    t1: t,
-    t2: t,
-  ): option => {
-    switch (t1, t2) {
-    | (DistributionTypes.Symbolic(d1), DistributionTypes.Symbolic(d2)) =>
-      Some(SymbolicDist.T.tryAnalyticalSimplification(d1, d2, arithmeticOperation))
-    | _ => None
-    }
-  }
-
-  let runDefault = (
-    t1: t,
+    ~strategy: StrategyChooser.specificStrategy,
     ~toPointSetFn: toPointSetFn,
     ~toSampleSetFn: toSampleSetFn,
-    ~arithmeticOperation,
-    ~t2: t,
   ): result => {
-    switch tryAnalyticalSimplification(arithmeticOperation, t1, t2) {
-    | Some(#AnalyticalSolution(symbolicDist)) => Ok(Symbolic(symbolicDist))
-    | Some(#Error(e)) => Error(OperationError(e))
-    | Some(#NoSolution)
-    | None =>
-      switch getInvalidOperationError(t1, t2, ~toPointSetFn, ~arithmeticOperation) {
-      | Some(e) => Error(e)
-      | None =>
-        switch chooseConvolutionOrMonteCarloDefault(arithmeticOperation, t1, t2) {
-        | MonteCarloStrat => runMonteCarlo(toSampleSetFn, arithmeticOperation, t1, t2)
-        | ConvolutionStrat(convOp) =>
-          runConvolution(toPointSetFn, convOp, t1, t2)->E.R2.fmap(r => DistributionTypes.PointSet(
-            r,
-          ))
-        }
+    switch strategy {
+    | #AsMonteCarlo =>
+      StrategyCallOnValidatedInputs.monteCarlo(toSampleSetFn, arithmeticOperation, t1, t2)
+    | #AsSymbolic =>
+      switch StrategyCallOnValidatedInputs.symbolic(arithmeticOperation, t1, t2) {
+      | #AnalyticalSolution(symbolicDist) => Ok(Symbolic(symbolicDist))
+      | #Error(e) => Error(OperationError(e))
+      | #NoSolution => Error(Unreachable)
+      }
+    | #AsConvolution =>
+      switch Operation.Convolution.fromAlgebraicOperation(arithmeticOperation) {
+      | Some(convOp) => StrategyCallOnValidatedInputs.convolution(toPointSetFn, convOp, t1, t2)
+      | None => Error(Unreachable)
       }
     }
   }
@@ -300,27 +321,38 @@ module AlgebraicCombination = {
     ~arithmeticOperation: Operation.algebraicOperation,
     ~t2: t,
   ): result => {
-    switch strategy {
-    | AsDefault => runDefault(t1, ~toPointSetFn, ~toSampleSetFn, ~arithmeticOperation, ~t2)
-    | AsSymbolic =>
-      switch tryAnalyticalSimplification(arithmeticOperation, t1, t2) {
-      | Some(#AnalyticalSolution(symbolicDist)) => Ok(Symbolic(symbolicDist))
-      | Some(#NoSolution) => Error(RequestedStrategyInvalidError(`No analytical solution`))
-      | None => Error(RequestedStrategyInvalidError("Inputs were not even symbolic"))
-      | Some(#Error(err)) => Error(OperationError(err))
+    let invalidOperationError = InputValidator.run(t1, t2, ~arithmeticOperation, ~toPointSetFn)
+    switch (invalidOperationError, strategy) {
+    | (Some(e), _) => Error(e)
+    | (None, AsDefault) => {
+        let chooseStrategy = StrategyChooser.run(~arithmeticOperation, ~t1, ~t2)
+        runStrategyOnValidatedInputs(
+          ~t1,
+          ~t2,
+          ~strategy=chooseStrategy,
+          ~arithmeticOperation,
+          ~toPointSetFn,
+          ~toSampleSetFn,
+        )
       }
-    | AsConvolution => {
-        let errString = opString => `Can't convolve on ${opString}`
-        switch arithmeticOperation {
-        | (#Add | #Subtract | #Multiply) as convOp =>
-          runConvolution(toPointSetFn, convOp, t1, t2)->E.R2.fmap(r => DistributionTypes.PointSet(
-            r,
-          ))
-        | (#Divide | #Power | #Logarithm) as op =>
-          op->Operation.Algebraic.toString->errString->RequestedStrategyInvalidError->Error
+    | (None, AsMonteCarlo) =>
+      StrategyCallOnValidatedInputs.monteCarlo(toSampleSetFn, arithmeticOperation, t1, t2)
+    | (None, AsSymbolic) =>
+      switch StrategyCallOnValidatedInputs.symbolic(arithmeticOperation, t1, t2) {
+      | #AnalyticalSolution(symbolicDist) => Ok(Symbolic(symbolicDist))
+      | #NoSolution => Error(RequestedStrategyInvalidError(`No analytic solution for inputs`))
+      | #Error(err) => Error(OperationError(err))
+      }
+    | (None, AsConvolution) =>
+      switch Operation.Convolution.fromAlgebraicOperation(arithmeticOperation) {
+      | None => {
+          let errString = `Convolution not supported for ${Operation.Algebraic.toString(
+              arithmeticOperation,
+            )}`
+          Error(RequestedStrategyInvalidError(errString))
         }
+      | Some(convOp) => StrategyCallOnValidatedInputs.convolution(toPointSetFn, convOp, t1, t2)
       }
-    | AsMonteCarlo => runMonteCarlo(toSampleSetFn, arithmeticOperation, t1, t2)
     }
   }
 }
diff --git a/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.resi b/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.resi
index ed2c5c03..e91803e2 100644
--- a/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.resi
+++ b/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.resi
@@ -69,3 +69,6 @@ let mixture: (
   ~scaleMultiplyFn: scaleMultiplyFn,
   ~pointwiseAddFn: pointwiseAddFn,
 ) => result
+
+let isSymbolic: t => bool
+let isPointSet: t => bool
diff --git a/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/AlgebraicShapeCombination.res b/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/AlgebraicShapeCombination.res
index 63600e43..a51de00d 100644
--- a/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/AlgebraicShapeCombination.res
+++ b/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/AlgebraicShapeCombination.res
@@ -263,4 +263,4 @@ let combineShapesContinuousDiscrete = (
   )
 }
 
-let isOrdered = (a: XYShape.T.t): bool => E.A.Sorted.Floats.isSorted(a.xs)
+let isOrdered = (a: XYShape.T.t): bool => E.A.Floats.isSorted(a.xs)
diff --git a/packages/squiggle-lang/src/rescript/Distributions/SampleSetDist/SampleSetDist_ToPointSet.res b/packages/squiggle-lang/src/rescript/Distributions/SampleSetDist/SampleSetDist_ToPointSet.res
index 90537a12..ec2bf0d0 100644
--- a/packages/squiggle-lang/src/rescript/Distributions/SampleSetDist/SampleSetDist_ToPointSet.res
+++ b/packages/squiggle-lang/src/rescript/Distributions/SampleSetDist/SampleSetDist_ToPointSet.res
@@ -39,28 +39,6 @@ module Internals = {
   module T = {
     type t = array
 
-    let splitContinuousAndDiscrete = (sortedArray: t) => {
-      let continuous = []
-      let discrete = E.FloatFloatMap.empty()
-      Belt.Array.forEachWithIndex(sortedArray, (index, element) => {
-        let maxIndex = (sortedArray |> Array.length) - 1
-        let possiblySimilarElements = switch index {
-        | 0 => [index + 1]
-        | n if n == maxIndex => [index - 1]
-        | _ => [index - 1, index + 1]
-        } |> Belt.Array.map(_, r => sortedArray[r])
-        let hasSimilarElement = Belt.Array.some(possiblySimilarElements, r => r == element)
-        hasSimilarElement
-          ? E.FloatFloatMap.increment(element, discrete)
-          : {
-              let _ = Js.Array.push(element, continuous)
-            }
-
-        ()
-      })
-      (continuous, discrete)
-    }
-
     let xWidthToUnitWidth = (samples, outputXYPoints, xWidth) => {
       let xyPointRange = E.A.Sorted.range(samples) |> E.O.default(0.0)
       let xyPointWidth = xyPointRange /. float_of_int(outputXYPoints)
@@ -85,7 +63,11 @@ let toPointSetDist = (
   (),
 ): Internals.Types.outputs => {
   Array.fast_sort(compare, samples)
-  let (continuousPart, discretePart) = E.A.Sorted.Floats.split(samples)
+  let minDiscreteToKeep = MagicNumbers.ToPointSet.minDiscreteToKeep(samples)
+  let (continuousPart, discretePart) = E.A.Floats.Sorted.splitContinuousAndDiscreteForMinWeight(
+    samples,
+    ~minDiscreteWeight=minDiscreteToKeep,
+  )
   let length = samples |> E.A.length |> float_of_int
   let discrete: PointSetTypes.discreteShape =
     discretePart
diff --git a/packages/squiggle-lang/src/rescript/MagicNumbers.res b/packages/squiggle-lang/src/rescript/MagicNumbers.res
index 291f05d6..124a44f4 100644
--- a/packages/squiggle-lang/src/rescript/MagicNumbers.res
+++ b/packages/squiggle-lang/src/rescript/MagicNumbers.res
@@ -22,3 +22,16 @@ module OpCost = {
   let wildcardCost = 1000
   let monteCarloCost = Environment.defaultSampleCount
 }
+
+module ToPointSet = {
+  /*
+  This function chooses the minimum amount of duplicate samples that need
+  to exist in order for this to be considered discrete. The tricky thing 
+  is that there are some operations that create duplicate continuous samples, 
+  so we can't guarantee that these only will occur because the fundamental 
+  structure is meant to be discrete. I chose this heuristic because I think 
+  it would strike a reasonable trade-off, but I’m really unsure what’s 
+  best right now.
+ */
+  let minDiscreteToKeep = samples => max(20, E.A.length(samples) / 50)
+}
diff --git a/packages/squiggle-lang/src/rescript/TypescriptInterface.res b/packages/squiggle-lang/src/rescript/TypescriptInterface.res
index a16e6875..70f3a3d1 100644
--- a/packages/squiggle-lang/src/rescript/TypescriptInterface.res
+++ b/packages/squiggle-lang/src/rescript/TypescriptInterface.res
@@ -13,6 +13,12 @@ type samplingParams = DistributionOperation.env
 @genType
 type genericDist = DistributionTypes.genericDist
 
+@genType
+type sampleSetDist = SampleSetDist.t
+
+@genType
+type symbolicDist = SymbolicDistTypes.symbolicDist
+
 @genType
 type distributionError = DistributionTypes.error
 
@@ -34,6 +40,12 @@ let evaluate = Reducer.evaluate
 @genType
 let evaluateUsingOptions = Reducer.evaluateUsingOptions
 
+@genType
+let evaluatePartialUsingExternalBindings = Reducer.evaluatePartialUsingExternalBindings
+
+@genType
+type externalBindings = Reducer.externalBindings
+
 @genType
 type expressionValue = ReducerInterface_ExpressionValue.expressionValue
 
diff --git a/packages/squiggle-lang/src/rescript/Utility/E.res b/packages/squiggle-lang/src/rescript/Utility/E.res
index 030c2961..e0bcaf5c 100644
--- a/packages/squiggle-lang/src/rescript/Utility/E.res
+++ b/packages/squiggle-lang/src/rescript/Utility/E.res
@@ -1,4 +1,7 @@
-open Rationale.Function.Infix
+/*
+Some functions from modules `L`, `O`, and `R` below were copied directly from
+running `rescript convert -all` on Rationale https://github.com/jonlaing/rationale
+*/
 module FloatFloatMap = {
   module Id = Belt.Id.MakeComparable({
     type t = float
@@ -8,7 +11,7 @@ module FloatFloatMap = {
   type t = Belt.MutableMap.t
 
   let fromArray = (ar: array<(float, float)>) => Belt.MutableMap.fromArray(ar, ~id=module(Id))
-  let toArray = (t: t) => Belt.MutableMap.toArray(t)
+  let toArray = (t: t): array<(float, float)> => Belt.MutableMap.toArray(t)
   let empty = () => Belt.MutableMap.make(~id=module(Id))
   let increment = (el, t: t) =>
     Belt.MutableMap.update(t, el, x =>
@@ -20,6 +23,10 @@ module FloatFloatMap = {
 
   let get = (el, t: t) => Belt.MutableMap.get(t, el)
   let fmap = (fn, t: t) => Belt.MutableMap.map(t, fn)
+  let partition = (fn, t: t) => {
+    let (match, noMatch) = Belt.Array.partition(toArray(t), fn)
+    (fromArray(match), fromArray(noMatch))
+  }
 }
 
 module Int = {
@@ -51,17 +58,59 @@ module O = {
     | None => rFn()
     }
   ()
-  let fmap = Rationale.Option.fmap
-  let bind = Rationale.Option.bind
-  let default = Rationale.Option.default
-  let isSome = Rationale.Option.isSome
-  let isNone = Rationale.Option.isNone
-  let toExn = Rationale.Option.toExn
-  let some = Rationale.Option.some
-  let firstSome = Rationale.Option.firstSome
-  let toExt = Rationale.Option.toExn // wanna flag this-- looks like a typo but `Rationale.OptiontoExt` doesn't exist.
-  let flatApply = (fn, b) => Rationale.Option.apply(fn, Some(b)) |> Rationale.Option.flatten
-  let flatten = Rationale.Option.flatten
+  let fmap = (f: 'a => 'b, x: option<'a>): option<'b> => {
+    switch x {
+    | None => None
+    | Some(x') => Some(f(x'))
+    }
+  }
+  let bind = (o, f) =>
+    switch o {
+    | None => None
+    | Some(a) => f(a)
+    }
+  let default = (d, o) =>
+    switch o {
+    | None => d
+    | Some(a) => a
+    }
+  let isSome = o =>
+    switch o {
+    | Some(_) => true
+    | _ => false
+    }
+  let isNone = o =>
+    switch o {
+    | None => true
+    | _ => false
+    }
+  let toExn = (err, o) =>
+    switch o {
+    | None => raise(Failure(err))
+    | Some(a) => a
+    }
+
+  let some = a => Some(a)
+  let firstSome = (a, b) =>
+    switch a {
+    | None => b
+    | _ => a
+    }
+
+  let toExt = toExn
+
+  let flatten = o =>
+    switch o {
+    | None => None
+    | Some(x) => x
+    }
+
+  let apply = (o, a) =>
+    switch o {
+    | Some(f) => bind(a, b => some(f(b)))
+    | _ => None
+    }
+  let flatApply = (fn, b) => apply(fn, Some(b)) |> flatten
 
   let toBool = opt =>
     switch opt {
@@ -109,6 +158,11 @@ module O2 = {
 
 /* Functions */
 module F = {
+  let pipe = (f, g, x) => g(f(x))
+  let compose = (f, g, x) => f(g(x))
+  let flip = (f, a, b) => f(b, a)
+  let always = (x, _y) => x
+
   let apply = (a, e) => a |> e
 
   let flatten2Callbacks = (fn1, fn2, fnlast) =>
@@ -156,10 +210,31 @@ exception Assertion(string)
 
 /* R for Result */
 module R = {
-  let result = Rationale.Result.result
+  open Belt.Result
+  let result = (okF, errF, r) =>
+    switch r {
+    | Ok(a) => okF(a)
+    | Error(err) => errF(err)
+    }
   let id = e => e |> result(U.id, U.id)
-  let fmap = Rationale.Result.fmap
-  let bind = Rationale.Result.bind
+  let isOk = Belt.Result.isOk
+  let getError = (r: result<'a, 'b>) =>
+    switch r {
+    | Ok(_) => None
+    | Error(e) => Some(e)
+    }
+  let fmap = (f: 'a => 'b, r: result<'a, 'c>): result<'b, 'c> => {
+    switch r {
+    | Ok(r') => Ok(f(r'))
+    | Error(err) => Error(err)
+    }
+  }
+  let bind = (r, f) =>
+    switch r {
+    | Ok(a) => f(a)
+    | Error(err) => Error(err)
+    }
+
   let toExn = (msg: string, x: result<'a, 'b>): 'a =>
     switch x {
     | Ok(r) => r
@@ -186,14 +261,17 @@ module R = {
   let errorIfCondition = (errorCondition, errorMessage, r) =>
     errorCondition(r) ? Error(errorMessage) : Ok(r)
 
-  let ap = Rationale.Result.ap
+  let ap = (r, a) =>
+    switch r {
+    | Ok(f) => Ok(f(a))
+    | Error(err) => Error(err)
+    }
   let ap' = (r, a) =>
     switch r {
     | Ok(f) => fmap(f, a)
     | Error(err) => Error(err)
     }
 
-  // (a1 -> a2 -> r) -> m a1 -> m a2 -> m r  // not in Rationale
   let liftM2: (('a, 'b) => 'c, result<'a, 'd>, result<'b, 'd>) => result<'c, 'd> = (op, xR, yR) => {
     ap'(fmap(op, xR), yR)
   }
@@ -243,7 +321,7 @@ module S = {
 }
 
 module J = {
-  let toString = \"||>"(Js.Json.decodeString, O.default(""))
+  let toString = F.pipe(Js.Json.decodeString, O.default(""))
   let fromString = Js.Json.string
   let fromNumber = Js.Json.number
 
@@ -256,7 +334,7 @@ module J = {
 
     let toString = (str: option<'a>) =>
       switch str {
-      | Some(str) => Some(str |> \"||>"(Js.Json.decodeString, O.default("")))
+      | Some(str) => Some(str |> F.pipe(Js.Json.decodeString, O.default("")))
       | _ => None
       }
   }
@@ -271,34 +349,132 @@ module JsDate = {
 
 /* List */
 module L = {
+  module Util = {
+    let eq = (a, b) => a == b
+  }
   let fmap = List.map
   let get = Belt.List.get
   let toArray = Array.of_list
   let fmapi = List.mapi
   let concat = List.concat
-  let drop = Rationale.RList.drop
-  let remove = Rationale.RList.remove
+  let concat' = (xs, ys) => List.append(ys, xs)
+
+  let rec drop = (i, xs) =>
+    switch (i, xs) {
+    | (_, list{}) => list{}
+    | (i, _) if i <= 0 => xs
+    | (i, list{_, ...b}) => drop(i - 1, b)
+    }
+
+  let append = (a, xs) => List.append(xs, list{a})
+  let take = {
+    let rec loop = (i, xs, acc) =>
+      switch (i, xs) {
+      | (i, _) if i <= 0 => acc
+      | (_, list{}) => acc
+      | (i, list{a, ...b}) => loop(i - 1, b, append(a, acc))
+      }
+    (i, xs) => loop(i, xs, list{})
+  }
+  let takeLast = (i, xs) => List.rev(xs) |> take(i) |> List.rev
+
+  let splitAt = (i, xs) => (take(i, xs), takeLast(List.length(xs) - i, xs))
+  let remove = (i, n, xs) => {
+    let (a, b) = splitAt(i, xs)
+    \"@"(a, drop(n, b))
+  }
+
   let find = List.find
   let filter = List.filter
   let for_all = List.for_all
   let exists = List.exists
   let sort = List.sort
   let length = List.length
-  let filter_opt = Rationale.RList.filter_opt
-  let uniqBy = Rationale.RList.uniqBy
-  let join = Rationale.RList.join
-  let head = Rationale.RList.head
-  let uniq = Rationale.RList.uniq
+
+  let filter_opt = xs => {
+    let rec loop = (l, acc) =>
+      switch l {
+      | list{} => acc
+      | list{hd, ...tl} =>
+        switch hd {
+        | None => loop(tl, acc)
+        | Some(x) => loop(tl, list{x, ...acc})
+        }
+      }
+    List.rev(loop(xs, list{}))
+  }
+
+  let containsWith = f => List.exists(f)
+
+  let uniqWithBy = (eq, f, xs) =>
+    List.fold_left(
+      ((acc, tacc), v) =>
+        containsWith(eq(f(v)), tacc) ? (acc, tacc) : (append(v, acc), append(f(v), tacc)),
+      (list{}, list{}),
+      xs,
+    ) |> fst
+
+  let uniqBy = (f, xs) => uniqWithBy(Util.eq, f, xs)
+  let join = j => List.fold_left((acc, v) => String.length(acc) == 0 ? v : acc ++ (j ++ v), "")
+
+  let head = xs =>
+    switch List.hd(xs) {
+    | exception _ => None
+    | a => Some(a)
+    }
+
+  let uniq = xs => uniqBy(x => x, xs)
   let flatten = List.flatten
-  let last = Rationale.RList.last
+  let last = xs => xs |> List.rev |> head
   let append = List.append
   let getBy = Belt.List.getBy
-  let dropLast = Rationale.RList.dropLast
-  let contains = Rationale.RList.contains
-  let without = Rationale.RList.without
-  let update = Rationale.RList.update
+  let dropLast = (i, xs) => take(List.length(xs) - i, xs)
+  let containsWith = f => List.exists(f)
+  let contains = x => containsWith(Util.eq(x))
+
+  let reject = pred => List.filter(x => !pred(x))
+  let tail = xs =>
+    switch List.tl(xs) {
+    | exception _ => None
+    | a => Some(a)
+    }
+
+  let init = xs => {
+    O.fmap(List.rev, xs |> List.rev |> tail)
+  }
+
+  let singleton = (x: 'a): list<'a> => list{x}
+
+  let adjust = (f, i, xs) => {
+    let (a, b) = splitAt(i + 1, xs)
+    switch a {
+    | _ if i < 0 => xs
+    | _ if i >= List.length(xs) => xs
+    | list{} => b
+    | list{a} => list{f(a), ...b}
+    | a =>
+      O.fmap(
+        concat'(b),
+        O.bind(init(a), x =>
+          O.fmap(F.flip(append, x), O.fmap(fmap(f), O.fmap(singleton, last(a))))
+        ),
+      ) |> O.default(xs)
+    }
+  }
+
+  let without = (exclude, xs) => reject(x => contains(x, exclude), xs)
+  let update = (x, i, xs) => adjust(F.always(x), i, xs)
   let iter = List.iter
-  let findIndex = Rationale.RList.findIndex
+
+  let findIndex = {
+    let rec loop = (pred, xs, i) =>
+      switch xs {
+      | list{} => None
+      | list{a, ...b} => pred(a) ? Some(i) : loop(pred, b, i + 1)
+      }
+    (pred, xs) => loop(pred, xs, 0)
+  }
+
   let headSafe = Belt.List.head
   let tailSafe = Belt.List.tail
   let headExn = Belt.List.headExn
@@ -360,7 +536,7 @@ module A = {
         Belt.Array.getUnsafe(a, index),
         Belt.Array.getUnsafe(a, index + 1),
       ))
-      |> Rationale.Result.return
+      |> (x => Ok(x))
     }
 
   let tail = Belt.Array.sliceToEnd(_, 1)
@@ -424,8 +600,8 @@ module A = {
   module O = {
     let concatSomes = (optionals: array>): array<'a> =>
       optionals
-      |> Js.Array.filter(Rationale.Option.isSome)
-      |> Js.Array.map(Rationale.Option.toExn("Warning: This should not have happened"))
+      |> Js.Array.filter(O.isSome)
+      |> Js.Array.map(O.toExn("Warning: This should not have happened"))
     let defaultEmpty = (o: option>): array<'a> =>
       switch o {
       | Some(o) => o
@@ -475,76 +651,8 @@ module A = {
     }
   }
 
-  module Sorted = {
-    let min = first
-    let max = last
-    let range = (~min=min, ~max=max, a) =>
-      switch (min(a), max(a)) {
-      | (Some(min), Some(max)) => Some(max -. min)
-      | _ => None
-      }
-
-    let floatCompare: (float, float) => int = compare
-
-    let binarySearchFirstElementGreaterIndex = (ar: array<'a>, el: 'a) => {
-      let el = Belt.SortArray.binarySearchBy(ar, el, floatCompare)
-      let el = el < 0 ? el * -1 - 1 : el
-      switch el {
-      | e if e >= length(ar) => #overMax
-      | e if e == 0 => #underMin
-      | e => #firstHigher(e)
-      }
-    }
-
-    let concat = (t1: array<'a>, t2: array<'a>) => {
-      let ts = Belt.Array.concat(t1, t2)
-      ts |> Array.fast_sort(floatCompare)
-      ts
-    }
-
-    let concatMany = (t1: array>) => {
-      let ts = Belt.Array.concatMany(t1)
-      ts |> Array.fast_sort(floatCompare)
-      ts
-    }
-
-    module Floats = {
-      let isSorted = (ar: array): bool =>
-        reduce(zip(ar, tail(ar)), true, (acc, (first, second)) => acc && first < second)
-
-      let makeIncrementalUp = (a, b) =>
-        Array.make(b - a + 1, a) |> Array.mapi((i, c) => c + i) |> Belt.Array.map(_, float_of_int)
-
-      let makeIncrementalDown = (a, b) =>
-        Array.make(a - b + 1, a) |> Array.mapi((i, c) => c - i) |> Belt.Array.map(_, float_of_int)
-
-      let split = (sortedArray: array) => {
-        let continuous = []
-        let discrete = FloatFloatMap.empty()
-        Belt.Array.forEachWithIndex(sortedArray, (_, element) => {
-          // let maxIndex = (sortedArray |> Array.length) - 1
-          // let possiblySimilarElements = switch index {
-          // | 0 => [index + 1]
-          // | n if n == maxIndex => [index - 1]
-          // | _ => [index - 1, index + 1]
-          // } |> Belt.Array.map(_, r => sortedArray[r])
-          // let hasSimilarElement = Belt.Array.some(possiblySimilarElements, r => r == element)
-          let hasSimilarElement = false
-          hasSimilarElement
-            ? FloatFloatMap.increment(element, discrete)
-            : {
-                let _ = Js.Array.push(element, continuous)
-              }
-
-          ()
-        })
-
-        (continuous, discrete)
-      }
-    }
-  }
-
   module Floats = {
+    type t = array
     let mean = Jstat.mean
     let geomean = Jstat.geomean
     let mode = Jstat.mode
@@ -553,14 +661,31 @@ module A = {
     let sum = Jstat.sum
     let random = Js.Math.random_int
 
+    let floatCompare: (float, float) => int = compare
+    let sort = t => {
+      let r = t
+      r |> Array.fast_sort(floatCompare)
+      r
+    }
+
+    let getNonFinite = (t: t) => Belt.Array.getBy(t, r => !Js.Float.isFinite(r))
+    let getBelowZero = (t: t) => Belt.Array.getBy(t, r => r < 0.0)
+
+    let isSorted = (t: t): bool =>
+      if Array.length(t) < 1 {
+        true
+      } else {
+        reduce(zip(t, tail(t)), true, (acc, (first, second)) => acc && first < second)
+      }
+
     //Passing true for the exclusive parameter excludes both endpoints of the range.
     //https://jstat.github.io/all.html
     let percentile = (a, b) => Jstat.percentile(a, b, false)
 
     // Gives an array with all the differences between values
     // diff([1,5,3,7]) = [4,-2,4]
-    let diff = (arr: array): array =>
-      Belt.Array.zipBy(arr, Belt.Array.sliceToEnd(arr, 1), (left, right) => right -. left)
+    let diff = (t: t): array =>
+      Belt.Array.zipBy(t, Belt.Array.sliceToEnd(t, 1), (left, right) => right -. left)
 
     exception RangeError(string)
     let range = (min: float, max: float, n: int): array =>
@@ -578,18 +703,104 @@ module A = {
 
     let min = Js.Math.minMany_float
     let max = Js.Math.maxMany_float
+
+    module Sorted = {
+      let min = first
+      let max = last
+      let range = (~min=min, ~max=max, a) =>
+        switch (min(a), max(a)) {
+        | (Some(min), Some(max)) => Some(max -. min)
+        | _ => None
+        }
+
+      let binarySearchFirstElementGreaterIndex = (ar: array<'a>, el: 'a) => {
+        let el = Belt.SortArray.binarySearchBy(ar, el, floatCompare)
+        let el = el < 0 ? el * -1 - 1 : el
+        switch el {
+        | e if e >= length(ar) => #overMax
+        | e if e == 0 => #underMin
+        | e => #firstHigher(e)
+        }
+      }
+
+      let concat = (t1: array<'a>, t2: array<'a>) => Belt.Array.concat(t1, t2)->sort
+
+      let concatMany = (t1: array>) => Belt.Array.concatMany(t1)->sort
+
+      let makeIncrementalUp = (a, b) =>
+        Array.make(b - a + 1, a) |> Array.mapi((i, c) => c + i) |> Belt.Array.map(_, float_of_int)
+
+      let makeIncrementalDown = (a, b) =>
+        Array.make(a - b + 1, a) |> Array.mapi((i, c) => c - i) |> Belt.Array.map(_, float_of_int)
+
+      /*
+      This function goes through a sorted array and divides it into two different clusters:
+      continuous samples and discrete samples. The discrete samples are stored in a mutable map.
+      Samples are thought to be discrete if they have any duplicates.
+ */
+      let _splitContinuousAndDiscreteForDuplicates = (sortedArray: array) => {
+        let continuous: array = []
+        let discrete = FloatFloatMap.empty()
+        Belt.Array.forEachWithIndex(sortedArray, (index, element) => {
+          let maxIndex = (sortedArray |> Array.length) - 1
+          let possiblySimilarElements = switch index {
+          | 0 => [index + 1]
+          | n if n == maxIndex => [index - 1]
+          | _ => [index - 1, index + 1]
+          } |> Belt.Array.map(_, r => sortedArray[r])
+          let hasSimilarElement = Belt.Array.some(possiblySimilarElements, r => r == element)
+          hasSimilarElement
+            ? FloatFloatMap.increment(element, discrete)
+            : {
+                let _ = Js.Array.push(element, continuous)
+              }
+
+          ()
+        })
+
+        (continuous, discrete)
+      }
+
+      /*
+      This function works very similarly to splitContinuousAndDiscreteForDuplicates. The one major difference
+      is that you can specify a minDiscreteWeight.  If the min discreet weight is 4, that would mean that
+      at least four elements needed from a specific value for that to be kept as discrete. This is important
+      because in some cases, we can expect that some common elements will be generated by regular operations.
+      The final continous array will be sorted.
+ */
+      let splitContinuousAndDiscreteForMinWeight = (
+        sortedArray: array,
+        ~minDiscreteWeight: int,
+      ) => {
+        let (continuous, discrete) = _splitContinuousAndDiscreteForDuplicates(sortedArray)
+        let keepFn = v => Belt.Float.toInt(v) >= minDiscreteWeight
+        let (discreteToKeep, discreteToIntegrate) = FloatFloatMap.partition(
+          ((_, v)) => keepFn(v),
+          discrete,
+        )
+        let newContinousSamples =
+          discreteToIntegrate->FloatFloatMap.toArray
+          |> fmap(((k, v)) => Belt.Array.makeBy(Belt.Float.toInt(v), _ => k))
+          |> Belt.Array.concatMany
+        let newContinuous = concat(continuous, newContinousSamples)
+        newContinuous |> Array.fast_sort(floatCompare)
+        (newContinuous, discreteToKeep)
+      }
+    }
   }
+  module Sorted = Floats.Sorted
 }
 
 module A2 = {
   let fmap = (a, b) => A.fmap(b, a)
   let joinWith = (a, b) => A.joinWith(b, a)
+  let filter = (a, b) => A.filter(b, a)
 }
 
 module JsArray = {
   let concatSomes = (optionals: Js.Array.t>): Js.Array.t<'a> =>
     optionals
-    |> Js.Array.filter(Rationale.Option.isSome)
-    |> Js.Array.map(Rationale.Option.toExn("Warning: This should not have happened"))
+    |> Js.Array.filter(O.isSome)
+    |> Js.Array.map(O.toExn("Warning: This should not have happened"))
   let filter = Js.Array.filter
 }
diff --git a/packages/squiggle-lang/src/rescript/Utility/Operation.res b/packages/squiggle-lang/src/rescript/Utility/Operation.res
index ac83ceea..4a1ef91a 100644
--- a/packages/squiggle-lang/src/rescript/Utility/Operation.res
+++ b/packages/squiggle-lang/src/rescript/Utility/Operation.res
@@ -29,6 +29,18 @@ type distToFloatOperation = [
 
 module Convolution = {
   type t = convolutionOperation
+  //Only a selection of operations are supported by convolution.
+  let fromAlgebraicOperation = (op: algebraicOperation): option =>
+    switch op {
+    | #Add => Some(#Add)
+    | #Subtract => Some(#Subtract)
+    | #Multiply => Some(#Multiply)
+    | #Divide | #Power | #Logarithm => None
+    }
+
+  let canDoAlgebraicOperation = (op: algebraicOperation): bool =>
+    fromAlgebraicOperation(op)->E.O.isSome
+
   let toFn: (t, float, float) => float = x =>
     switch x {
     | #Add => \"+."
diff --git a/packages/squiggle-lang/src/rescript/Utility/XYShape.res b/packages/squiggle-lang/src/rescript/Utility/XYShape.res
index 97974884..1f1e87ca 100644
--- a/packages/squiggle-lang/src/rescript/Utility/XYShape.res
+++ b/packages/squiggle-lang/src/rescript/Utility/XYShape.res
@@ -4,6 +4,42 @@ type xyShape = {
   ys: array,
 }
 
+type propertyName = string
+
+@genType
+type rec error =
+  | NotSorted(propertyName)
+  | IsEmpty(propertyName)
+  | NotFinite(propertyName, float)
+  | DifferentLengths({p1Name: string, p2Name: string, p1Length: int, p2Length: int})
+  | MultipleErrors(array)
+
+@genType
+module Error = {
+  let mapErrorArrayToError = (errors: array): option => {
+    switch errors {
+    | [] => None
+    | [error] => Some(error)
+    | _ => Some(MultipleErrors(errors))
+    }
+  }
+
+  let rec toString = (t: error) =>
+    switch t {
+    | NotSorted(propertyName) => `${propertyName} is not sorted`
+    | IsEmpty(propertyName) => `${propertyName} is empty`
+    | NotFinite(propertyName, exampleValue) =>
+      `${propertyName} is not finite. Example value: ${E.Float.toString(exampleValue)}`
+    | DifferentLengths({p1Name, p2Name, p1Length, p2Length}) =>
+      `${p1Name} and ${p2Name} have different lengths. ${p1Name} has length ${E.I.toString(
+          p1Length,
+        )} and ${p2Name} has length ${E.I.toString(p2Length)}`
+    | MultipleErrors(errors) =>
+      `Multiple Errors: ${E.A2.fmap(errors, toString)->E.A2.fmap(r => `[${r}]`)
+          |> E.A.joinWith(", ")}`
+    }
+}
+
 @genType
 type interpolationStrategy = [
   | #Stepwise
@@ -60,6 +96,44 @@ module T = {
   let fromZippedArray = (pairs: array<(float, float)>): t => pairs |> Belt.Array.unzip |> fromArray
   let equallyDividedXs = (t: t, newLength) => E.A.Floats.range(minX(t), maxX(t), newLength)
   let toJs = (t: t) => {"xs": t.xs, "ys": t.ys}
+
+  module Validator = {
+    let fnName = "XYShape validate"
+    let notSortedError = (p: string): error => NotSorted(p)
+    let notFiniteError = (p, exampleValue): error => NotFinite(p, exampleValue)
+    let isEmptyError = (propertyName): error => IsEmpty(propertyName)
+    let differentLengthsError = (t): error => DifferentLengths({
+      p1Name: "Xs",
+      p2Name: "Ys",
+      p1Length: E.A.length(xs(t)),
+      p2Length: E.A.length(ys(t)),
+    })
+
+    let areXsSorted = (t: t) => E.A.Floats.isSorted(xs(t))
+    let areXsEmpty = (t: t) => E.A.length(xs(t)) == 0
+    let getNonFiniteXs = (t: t) => t->xs->E.A.Floats.getNonFinite
+    let getNonFiniteYs = (t: t) => t->ys->E.A.Floats.getNonFinite
+
+    let validate = (t: t) => {
+      let xsNotSorted = areXsSorted(t) ? None : Some(notSortedError("Xs"))
+      let xsEmpty = areXsEmpty(t) ? Some(isEmptyError("Xs")) : None
+      let differentLengths =
+        E.A.length(xs(t)) !== E.A.length(ys(t)) ? Some(differentLengthsError(t)) : None
+      let xsNotFinite = getNonFiniteXs(t)->E.O2.fmap(notFiniteError("Xs"))
+      let ysNotFinite = getNonFiniteYs(t)->E.O2.fmap(notFiniteError("Ys"))
+      [xsNotSorted, xsEmpty, differentLengths, xsNotFinite, ysNotFinite]
+      ->E.A.O.concatSomes
+      ->Error.mapErrorArrayToError
+    }
+  }
+
+  let make = (~xs: array, ~ys: array) => {
+    let attempt: t = {xs: xs, ys: ys}
+    switch Validator.validate(attempt) {
+    | Some(error) => Error(error)
+    | None => Ok(attempt)
+    }
+  }
 }
 
 module Ts = {
diff --git a/packages/website/docs/Discussions/Gallery.md b/packages/website/docs/Discussions/Gallery.md
new file mode 100644
index 00000000..fee8f344
--- /dev/null
+++ b/packages/website/docs/Discussions/Gallery.md
@@ -0,0 +1,7 @@
+---
+sidebar_position: 6
+title: Gallery
+---
+
+- [Adjusting probabilities for the passage of time](https://www.lesswrong.com/s/rDe8QE5NvXcZYzgZ3/p/j8o6sgRerE3tqNWdj) by Nuño Sempere
+- [GiveWell's GiveDirectly cost effectiveness analysis](https://observablehq.com/@hazelfire/givewells-givedirectly-cost-effectiveness-analysis) by Sam Nolan
diff --git a/packages/website/docs/Discussions/Three-Types-Of-Distributions.md b/packages/website/docs/Discussions/Three-Formats-Of-Distributions.md
similarity index 99%
rename from packages/website/docs/Discussions/Three-Types-Of-Distributions.md
rename to packages/website/docs/Discussions/Three-Formats-Of-Distributions.md
index ffd99fc1..8ec5b88d 100644
--- a/packages/website/docs/Discussions/Three-Types-Of-Distributions.md
+++ b/packages/website/docs/Discussions/Three-Formats-Of-Distributions.md
@@ -1,12 +1,10 @@
 ---
 sidebar_position: 5
+title: Three Formats of Distributions
+author: Ozzie Gooen
+date: 02-19-2022
 ---
 
-# Three Formats of Distributions
-
-_Author: Ozzie Gooen_  
-_Written on: Feb 19, 2022_
-
 Probability distributions have several subtle possible formats. Three important ones that we deal with in Squiggle are symbolic, sample set, and graph formats.
 
 _Symbolic_ formats are just the math equations. `normal(5,3)` is the symbolic representation of a normal distribution.
diff --git a/packages/website/docs/Features/Language.mdx b/packages/website/docs/Features/Language.mdx
index e559fb60..5b66d2e2 100644
--- a/packages/website/docs/Features/Language.mdx
+++ b/packages/website/docs/Features/Language.mdx
@@ -1,39 +1,53 @@
 ---
 sidebar_position: 2
+title: Language Basics
 ---
 
 import { SquiggleEditor } from "../../src/components/SquiggleEditor";
 
-# Squiggle Language
+## Expressions
 
-The squiggle language has a very simple syntax. The best way to get to understand
-it is by simply looking at examples.
+A distribution
 
-## Basic Language
+ = `
 
 
-  
-    
-       
-    
-      
-        
-           
-        
-           
-        
-           
-        
-          
-             
-           
-        
-           
-         
-       
-   
- 
diff --git a/packages/website/static/img/undraw_docusaurus_react.svg b/packages/website/static/img/undraw_docusaurus_react.svg
deleted file mode 100644
index e4170504..00000000
--- a/packages/website/static/img/undraw_docusaurus_react.svg
+++ /dev/null
@@ -1,169 +0,0 @@
-
-  
-    
-       
-    
-      
-        
-           
-        
-           
-        
-           
-        
-          
-             
-           
-        
-           
-         
-       
-    
-       
-   
- 
diff --git a/packages/website/static/img/undraw_docusaurus_tree.svg b/packages/website/static/img/undraw_docusaurus_tree.svg
deleted file mode 100644
index a05cc03d..00000000
--- a/packages/website/static/img/undraw_docusaurus_tree.svg
+++ /dev/null
@@ -1 +0,0 @@
-docu_tree