diff --git a/packages/squiggle-lang/__tests__/Hardcoded__Test.re b/packages/squiggle-lang/__tests__/Hardcoded__Test.res similarity index 97% rename from packages/squiggle-lang/__tests__/Hardcoded__Test.re rename to packages/squiggle-lang/__tests__/Hardcoded__Test.res index 1e44df7a..482019bc 100644 --- a/packages/squiggle-lang/__tests__/Hardcoded__Test.re +++ b/packages/squiggle-lang/__tests__/Hardcoded__Test.res @@ -1,6 +1,6 @@ -open Jest; -open Expect; -/* +open Jest +open Expect +/* let makeTest = (~only=false, str, item1, item2) => only ? Only.test(str, () => diff --git a/packages/squiggle-lang/__tests__/Lodash__test.re b/packages/squiggle-lang/__tests__/Lodash__test.re deleted file mode 100644 index bf93ea0a..00000000 --- a/packages/squiggle-lang/__tests__/Lodash__test.re +++ /dev/null @@ -1,25 +0,0 @@ -open Jest; -open Expect; - -let makeTest = (~only=false, str, item1, item2) => - only - ? Only.test(str, () => - expect(item1) |> toEqual(item2) - ) - : test(str, () => - expect(item1) |> toEqual(item2) - ); - -describe("Lodash", () => { - describe("Lodash", () => { - makeTest(~only=true, "Foo", Jstat.Normal.mean(5.0,2.0), 5.0); - makeTest("min", Lodash.min([|1, 3, 4|]), 1); - makeTest("max", Lodash.max([|1, 3, 4|]), 4); - makeTest("uniq", Lodash.uniq([|1, 3, 4, 4|]), [|1, 3, 4|]); - makeTest( - "countBy", - Lodash.countBy([|1, 3, 4, 4|], r => r), - Js.Dict.fromArray([|("1", 1), ("3", 1), ("4", 2)|]), - ); - }) -}); \ No newline at end of file diff --git a/packages/squiggle-lang/__tests__/Lodash__test.res b/packages/squiggle-lang/__tests__/Lodash__test.res new file mode 100644 index 00000000..141c4789 --- /dev/null +++ b/packages/squiggle-lang/__tests__/Lodash__test.res @@ -0,0 +1,20 @@ +open Jest +open Expect + +let makeTest = (~only=false, str, item1, item2) => + only + ? Only.test(str, () => expect(item1) |> toEqual(item2)) + : test(str, () => expect(item1) |> toEqual(item2)) + +describe("Lodash", () => + describe("Lodash", () => { + makeTest("min", Lodash.min([1, 3, 4]), 1) + makeTest("max", Lodash.max([1, 3, 4]), 4) + makeTest("uniq", Lodash.uniq([1, 3, 4, 4]), [1, 3, 4]) + makeTest( + "countBy", + Lodash.countBy([1, 3, 4, 4], r => r), + Js.Dict.fromArray([("1", 1), ("3", 1), ("4", 2)]), + ) + }) +) diff --git a/packages/squiggle-lang/src/distPlus/typeSystem/HardcodedFunctions.re b/packages/squiggle-lang/src/distPlus/typeSystem/HardcodedFunctions.re deleted file mode 100644 index 4c6625ca..00000000 --- a/packages/squiggle-lang/src/distPlus/typeSystem/HardcodedFunctions.re +++ /dev/null @@ -1,258 +0,0 @@ -open TypeSystem; - -let wrongInputsError = (r: array(typedValue)) => { - let inputs = r |> E.A.fmap(TypedValue.toString) |>Js.String.concatMany(_, ","); - Js.log3("Inputs were", inputs, r); - Error("Wrong inputs. The inputs were:" ++ inputs); -}; - -let to_: (float, float) => result(node, string) = - (low, high) => - switch (low, high) { - | (low, high) when low <= 0.0 && low < high => - Ok(`SymbolicDist(SymbolicDist.Normal.from90PercentCI(low, high))) - | (low, high) when low < high => - Ok(`SymbolicDist(SymbolicDist.Lognormal.from90PercentCI(low, high))) - | (_, _) => Error("Low value must be less than high value.") - }; - -let makeSymbolicFromTwoFloats = (name, fn) => - Function.T.make( - ~name, - ~outputType=`SamplingDistribution, - ~inputTypes=[|`Float, `Float|], - ~run= - fun - | [|`Float(a), `Float(b)|] => Ok(`SymbolicDist(fn(a, b))) - | e => wrongInputsError(e), - (), - ); - -let makeSymbolicFromOneFloat = (name, fn) => - Function.T.make( - ~name, - ~outputType=`SamplingDistribution, - ~inputTypes=[|`Float|], - ~run= - fun - | [|`Float(a)|] => Ok(`SymbolicDist(fn(a))) - | e => wrongInputsError(e), - (), - ); - -let makeDistFloat = (name, fn) => - Function.T.make( - ~name, - ~outputType=`SamplingDistribution, - ~inputTypes=[|`SamplingDistribution, `Float|], - ~run= - fun - | [|`SamplingDist(a), `Float(b)|] => fn(a, b) - | [|`RenderedDist(a), `Float(b)|] => fn(`RenderedDist(a), b) - | e => wrongInputsError(e), - (), - ); - -let makeRenderedDistFloat = (name, fn) => - Function.T.make( - ~name, - ~outputType=`RenderedDistribution, - ~inputTypes=[|`RenderedDistribution, `Float|], - ~shouldCoerceTypes=true, - ~run= - fun - | [|`RenderedDist(a), `Float(b)|] => fn(a, b) - | e => wrongInputsError(e), - (), - ); - -let makeDist = (name, fn) => - Function.T.make( - ~name, - ~outputType=`SamplingDistribution, - ~inputTypes=[|`SamplingDistribution|], - ~run= - fun - | [|`SamplingDist(a)|] => fn(a) - | [|`RenderedDist(a)|] => fn(`RenderedDist(a)) - | e => wrongInputsError(e), - (), - ); - -let floatFromDist = - ( - distToFloatOp: ExpressionTypes.distToFloatOperation, - t: TypeSystem.samplingDist, - ) - : result(node, string) => { - switch (t) { - | `SymbolicDist(s) => - SymbolicDist.T.operate(distToFloatOp, s) - |> E.R.bind(_, v => Ok(`SymbolicDist(`Float(v)))) - | `RenderedDist(rs) => - Shape.operate(distToFloatOp, rs) |> (v => Ok(`SymbolicDist(`Float(v)))) - }; -}; - -let verticalScaling = (scaleOp, rs, scaleBy) => { - // scaleBy has to be a single float, otherwise we'll return an error. - let fn = (secondary, main) => - Operation.Scale.toFn(scaleOp, main, secondary); - let integralSumCacheFn = Operation.Scale.toIntegralSumCacheFn(scaleOp); - let integralCacheFn = Operation.Scale.toIntegralCacheFn(scaleOp); - Ok( - `RenderedDist( - Shape.T.mapY( - ~integralSumCacheFn=integralSumCacheFn(scaleBy), - ~integralCacheFn=integralCacheFn(scaleBy), - ~fn=fn(scaleBy), - rs, - ), - ), - ); -}; - -module Multimodal = { - let getByNameResult = ExpressionTypes.ExpressionTree.Hash.getByNameResult; - - let _paramsToDistsAndWeights = (r: array(typedValue)) => - switch (r) { - | [|`Hash(r)|] => - let dists = - getByNameResult(r, "dists") - ->E.R.bind(TypeSystem.TypedValue.toArray) - ->E.R.bind(r => - r - |> E.A.fmap(TypeSystem.TypedValue.toDist) - |> E.A.R.firstErrorOrOpen - ); - let weights = - getByNameResult(r, "weights") - ->E.R.bind(TypeSystem.TypedValue.toArray) - ->E.R.bind(r => - r - |> E.A.fmap(TypeSystem.TypedValue.toFloat) - |> E.A.R.firstErrorOrOpen - ); - - E.R.merge(dists, weights) - |> E.R.fmap(((a, b)) => - E.A.zipMaxLength(a, b) - |> E.A.fmap(((a, b)) => - (a |> E.O.toExn(""), b |> E.O.default(1.0)) - ) - ); - | _ => Error("Needs items") - }; - let _runner: array(typedValue) => result(node, string) = - r => { - let paramsToDistsAndWeights = - _paramsToDistsAndWeights(r) - |> E.R.fmap( - E.A.fmap(((dist, weight)) => - `FunctionCall(( - "scaleMultiply", - [|dist, `SymbolicDist(`Float(weight))|], - )) - ), - ); - let pointwiseSum: result(node, string) = - paramsToDistsAndWeights->E.R.bind( - E.R.errorIfCondition(E.A.isEmpty, "Needs one input"), - ) - |> E.R.fmap(r => - r - |> Js.Array.sliceFrom(1) - |> E.A.fold_left( - (acc, x) => {`PointwiseCombination((`Add, acc, x))}, - E.A.unsafe_get(r, 0), - ) - ); - pointwiseSum; - }; - - let _function = - Function.T.make( - ~name="multimodal", - ~outputType=`SamplingDistribution, - ~inputTypes=[| - `Hash([| - ("dists", `Array(`SamplingDistribution)), - ("weights", `Array(`Float)), - |]), - |], - ~run=_runner, - (), - ); -}; - -let all = [| - makeSymbolicFromTwoFloats("normal", SymbolicDist.Normal.make), - makeSymbolicFromTwoFloats("uniform", SymbolicDist.Uniform.make), - makeSymbolicFromTwoFloats("beta", SymbolicDist.Beta.make), - makeSymbolicFromTwoFloats("lognormal", SymbolicDist.Lognormal.make), - makeSymbolicFromTwoFloats( - "lognormalFromMeanAndStdDev", - SymbolicDist.Lognormal.fromMeanAndStdev, - ), - makeSymbolicFromOneFloat("exponential", SymbolicDist.Exponential.make), - Function.T.make( - ~name="to", - ~outputType=`SamplingDistribution, - ~inputTypes=[|`Float, `Float|], - ~run= - fun - | [|`Float(a), `Float(b)|] => to_(a, b) - | e => wrongInputsError(e), - (), - ), - Function.T.make( - ~name="triangular", - ~outputType=`SamplingDistribution, - ~inputTypes=[|`Float, `Float, `Float|], - ~run= - fun - | [|`Float(a), `Float(b), `Float(c)|] => - SymbolicDist.Triangular.make(a, b, c) - |> E.R.fmap(r => `SymbolicDist(r)) - | e => wrongInputsError(e), - (), - ), - makeDistFloat("pdf", (dist, float) => floatFromDist(`Pdf(float), dist)), - makeDistFloat("inv", (dist, float) => floatFromDist(`Inv(float), dist)), - makeDistFloat("cdf", (dist, float) => floatFromDist(`Cdf(float), dist)), - makeDist("mean", dist => floatFromDist(`Mean, dist)), - makeDist("sample", dist => floatFromDist(`Sample, dist)), - Function.T.make( - ~name="render", - ~outputType=`RenderedDistribution, - ~inputTypes=[|`RenderedDistribution|], - ~run= - fun - | [|`RenderedDist(c)|] => Ok(`RenderedDist(c)) - | e => wrongInputsError(e), - (), - ), - Function.T.make( - ~name="normalize", - ~outputType=`SamplingDistribution, - ~inputTypes=[|`SamplingDistribution|], - ~run= - fun - | [|`SamplingDist(`SymbolicDist(c))|] => Ok(`SymbolicDist(c)) - | [|`SamplingDist(`RenderedDist(c))|] => - Ok(`RenderedDist(Shape.T.normalize(c))) - | e => wrongInputsError(e), - (), - ), - makeRenderedDistFloat("scaleExp", (dist, float) => - verticalScaling(`Exponentiate, dist, float) - ), - makeRenderedDistFloat("scaleMultiply", (dist, float) => - verticalScaling(`Multiply, dist, float) - ), - makeRenderedDistFloat("scaleLog", (dist, float) => - verticalScaling(`Log, dist, float) - ), - Multimodal._function -|]; diff --git a/packages/squiggle-lang/src/distPlus/typeSystem/HardcodedFunctions.res b/packages/squiggle-lang/src/distPlus/typeSystem/HardcodedFunctions.res new file mode 100644 index 00000000..165dfabb --- /dev/null +++ b/packages/squiggle-lang/src/distPlus/typeSystem/HardcodedFunctions.res @@ -0,0 +1,221 @@ +open TypeSystem + +let wrongInputsError = (r: array) => { + let inputs = r |> E.A.fmap(TypedValue.toString) |> Js.String.concatMany(_, ",") + Js.log3("Inputs were", inputs, r) + Error("Wrong inputs. The inputs were:" ++ inputs) +} + +let to_: (float, float) => result = (low, high) => + switch (low, high) { + | (low, high) if low <= 0.0 && low < high => + Ok(#SymbolicDist(SymbolicDist.Normal.from90PercentCI(low, high))) + | (low, high) if low < high => + Ok(#SymbolicDist(SymbolicDist.Lognormal.from90PercentCI(low, high))) + | (_, _) => Error("Low value must be less than high value.") + } + +let makeSymbolicFromTwoFloats = (name, fn) => + Function.T.make( + ~name, + ~outputType=#SamplingDistribution, + ~inputTypes=[#Float, #Float], + ~run=x => + switch x { + | [#Float(a), #Float(b)] => Ok(#SymbolicDist(fn(a, b))) + | e => wrongInputsError(e) + }, + (), + ) + +let makeSymbolicFromOneFloat = (name, fn) => + Function.T.make( + ~name, + ~outputType=#SamplingDistribution, + ~inputTypes=[#Float], + ~run=x => + switch x { + | [#Float(a)] => Ok(#SymbolicDist(fn(a))) + | e => wrongInputsError(e) + }, + (), + ) + +let makeDistFloat = (name, fn) => + Function.T.make( + ~name, + ~outputType=#SamplingDistribution, + ~inputTypes=[#SamplingDistribution, #Float], + ~run=x => + switch x { + | [#SamplingDist(a), #Float(b)] => fn(a, b) + | [#RenderedDist(a), #Float(b)] => fn(#RenderedDist(a), b) + | e => wrongInputsError(e) + }, + (), + ) + +let makeRenderedDistFloat = (name, fn) => + Function.T.make( + ~name, + ~outputType=#RenderedDistribution, + ~inputTypes=[#RenderedDistribution, #Float], + ~shouldCoerceTypes=true, + ~run=x => + switch x { + | [#RenderedDist(a), #Float(b)] => fn(a, b) + | e => wrongInputsError(e) + }, + (), + ) + +let makeDist = (name, fn) => + Function.T.make( + ~name, + ~outputType=#SamplingDistribution, + ~inputTypes=[#SamplingDistribution], + ~run=x => + switch x { + | [#SamplingDist(a)] => fn(a) + | [#RenderedDist(a)] => fn(#RenderedDist(a)) + | e => wrongInputsError(e) + }, + (), + ) + +let floatFromDist = ( + distToFloatOp: ExpressionTypes.distToFloatOperation, + t: TypeSystem.samplingDist, +): result => + switch t { + | #SymbolicDist(s) => + SymbolicDist.T.operate(distToFloatOp, s) |> E.R.bind(_, v => Ok(#SymbolicDist(#Float(v)))) + | #RenderedDist(rs) => Shape.operate(distToFloatOp, rs) |> (v => Ok(#SymbolicDist(#Float(v)))) + } + +let verticalScaling = (scaleOp, rs, scaleBy) => { + // scaleBy has to be a single float, otherwise we'll return an error. + let fn = (secondary, main) => Operation.Scale.toFn(scaleOp, main, secondary) + let integralSumCacheFn = Operation.Scale.toIntegralSumCacheFn(scaleOp) + let integralCacheFn = Operation.Scale.toIntegralCacheFn(scaleOp) + Ok( + #RenderedDist( + Shape.T.mapY( + ~integralSumCacheFn=integralSumCacheFn(scaleBy), + ~integralCacheFn=integralCacheFn(scaleBy), + ~fn=fn(scaleBy), + rs, + ), + ), + ) +} + +module Multimodal = { + let getByNameResult = ExpressionTypes.ExpressionTree.Hash.getByNameResult + + let _paramsToDistsAndWeights = (r: array) => + switch r { + | [#Hash(r)] => + let dists = + getByNameResult(r, "dists") + ->E.R.bind(TypeSystem.TypedValue.toArray) + ->E.R.bind(r => r |> E.A.fmap(TypeSystem.TypedValue.toDist) |> E.A.R.firstErrorOrOpen) + let weights = + getByNameResult(r, "weights") + ->E.R.bind(TypeSystem.TypedValue.toArray) + ->E.R.bind(r => r |> E.A.fmap(TypeSystem.TypedValue.toFloat) |> E.A.R.firstErrorOrOpen) + + E.R.merge(dists, weights) |> E.R.fmap(((a, b)) => + E.A.zipMaxLength(a, b) |> E.A.fmap(((a, b)) => (a |> E.O.toExn(""), b |> E.O.default(1.0))) + ) + | _ => Error("Needs items") + } + let _runner: array => result = r => { + let paramsToDistsAndWeights = + _paramsToDistsAndWeights(r) |> E.R.fmap( + E.A.fmap(((dist, weight)) => + #FunctionCall("scaleMultiply", [dist, #SymbolicDist(#Float(weight))]) + ), + ) + let pointwiseSum: result = + paramsToDistsAndWeights->E.R.bind(E.R.errorIfCondition(E.A.isEmpty, "Needs one input")) + |> E.R.fmap(r => + r + |> Js.Array.sliceFrom(1) + |> E.A.fold_left((acc, x) => #PointwiseCombination(#Add, acc, x), E.A.unsafe_get(r, 0)) + ) + pointwiseSum + } + + let _function = Function.T.make( + ~name="multimodal", + ~outputType=#SamplingDistribution, + ~inputTypes=[#Hash([("dists", #Array(#SamplingDistribution)), ("weights", #Array(#Float))])], + ~run=_runner, + (), + ) +} + +let all = [ + makeSymbolicFromTwoFloats("normal", SymbolicDist.Normal.make), + makeSymbolicFromTwoFloats("uniform", SymbolicDist.Uniform.make), + makeSymbolicFromTwoFloats("beta", SymbolicDist.Beta.make), + makeSymbolicFromTwoFloats("lognormal", SymbolicDist.Lognormal.make), + makeSymbolicFromTwoFloats("lognormalFromMeanAndStdDev", SymbolicDist.Lognormal.fromMeanAndStdev), + makeSymbolicFromOneFloat("exponential", SymbolicDist.Exponential.make), + Function.T.make( + ~name="to", + ~outputType=#SamplingDistribution, + ~inputTypes=[#Float, #Float], + ~run=x => + switch x { + | [#Float(a), #Float(b)] => to_(a, b) + | e => wrongInputsError(e) + }, + (), + ), + Function.T.make( + ~name="triangular", + ~outputType=#SamplingDistribution, + ~inputTypes=[#Float, #Float, #Float], + ~run=x => + switch x { + | [#Float(a), #Float(b), #Float(c)] => + SymbolicDist.Triangular.make(a, b, c) |> E.R.fmap(r => #SymbolicDist(r)) + | e => wrongInputsError(e) + }, + (), + ), + makeDistFloat("pdf", (dist, float) => floatFromDist(#Pdf(float), dist)), + makeDistFloat("inv", (dist, float) => floatFromDist(#Inv(float), dist)), + makeDistFloat("cdf", (dist, float) => floatFromDist(#Cdf(float), dist)), + makeDist("mean", dist => floatFromDist(#Mean, dist)), + makeDist("sample", dist => floatFromDist(#Sample, dist)), + Function.T.make( + ~name="render", + ~outputType=#RenderedDistribution, + ~inputTypes=[#RenderedDistribution], + ~run=x => + switch x { + | [#RenderedDist(c)] => Ok(#RenderedDist(c)) + | e => wrongInputsError(e) + }, + (), + ), + Function.T.make( + ~name="normalize", + ~outputType=#SamplingDistribution, + ~inputTypes=[#SamplingDistribution], + ~run=x => + switch x { + | [#SamplingDist(#SymbolicDist(c))] => Ok(#SymbolicDist(c)) + | [#SamplingDist(#RenderedDist(c))] => Ok(#RenderedDist(Shape.T.normalize(c))) + | e => wrongInputsError(e) + }, + (), + ), + makeRenderedDistFloat("scaleExp", (dist, float) => verticalScaling(#Exponentiate, dist, float)), + makeRenderedDistFloat("scaleMultiply", (dist, float) => verticalScaling(#Multiply, dist, float)), + makeRenderedDistFloat("scaleLog", (dist, float) => verticalScaling(#Log, dist, float)), + Multimodal._function, +] diff --git a/packages/squiggle-lang/src/distPlus/typeSystem/TypeSystem.re b/packages/squiggle-lang/src/distPlus/typeSystem/TypeSystem.re deleted file mode 100644 index ee78e986..00000000 --- a/packages/squiggle-lang/src/distPlus/typeSystem/TypeSystem.re +++ /dev/null @@ -1,228 +0,0 @@ -type node = ExpressionTypes.ExpressionTree.node; -let getFloat = ExpressionTypes.ExpressionTree.getFloat; - -type samplingDist = [ - | `SymbolicDist(SymbolicTypes.symbolicDist) - | `RenderedDist(DistTypes.shape) -]; - -type hashType = array((string, _type)) -and _type = [ - | `Float - | `SamplingDistribution - | `RenderedDistribution - | `Array(_type) - | `Hash(hashType) -]; - -type hashTypedValue = array((string, typedValue)) -and typedValue = [ - | `Float(float) - | `RenderedDist(DistTypes.shape) - | `SamplingDist(samplingDist) - | `Array(array(typedValue)) - | `Hash(hashTypedValue) -]; - -type _function = { - name: string, - inputTypes: array(_type), - outputType: _type, - run: array(typedValue) => result(node, string), - shouldCoerceTypes: bool, -}; - -type functions = array(_function); -type inputNodes = array(node); - -module TypedValue = { - let rec toString: typedValue => string = - fun - | `SamplingDist(_) => "[sampling dist]" - | `RenderedDist(_) => "[rendered Shape]" - | `Float(f) => "Float: " ++ Js.Float.toString(f) - | `Array(a) => - "[" ++ (a |> E.A.fmap(toString) |> Js.String.concatMany(_, ",")) ++ "]" - | `Hash(v) => - "{" - ++ ( - v - |> E.A.fmap(((name, value)) => name ++ ":" ++ toString(value)) - |> Js.String.concatMany(_, ",") - ) - ++ "}"; - - let rec fromNode = (node: node): result(typedValue, string) => - switch (node) { - | `SymbolicDist(`Float(r)) => Ok(`Float(r)) - | `SymbolicDist(s) => Ok(`SamplingDist(`SymbolicDist(s))) - | `RenderedDist(s) => Ok(`RenderedDist(s)) - | `Array(r) => - r - |> E.A.fmap(fromNode) - |> E.A.R.firstErrorOrOpen - |> E.R.fmap(r => `Array(r)) - | `Hash(hash) => - hash - |> E.A.fmap(((name, t)) => fromNode(t) |> E.R.fmap(r => (name, r))) - |> E.A.R.firstErrorOrOpen - |> E.R.fmap(r => `Hash(r)) - | e => Error("Wrong type: " ++ ExpressionTreeBasic.toString(e)) - }; - - // todo: Arrays and hashes - let rec fromNodeWithTypeCoercion = (evaluationParams, _type: _type, node) => { - switch (_type, node) { - | (`Float, _) => - switch (getFloat(node)) { - | Some(a) => Ok(`Float(a)) - | _ => Error("Type Error: Expected float.") - } - | (`SamplingDistribution, _) => - PTypes.SamplingDistribution.renderIfIsNotSamplingDistribution( - evaluationParams, - node, - ) - |> E.R.bind(_, fromNode) - | (`RenderedDistribution, _) =>{ - ExpressionTypes.ExpressionTree.Render.render(evaluationParams, node) - |> E.R.bind(_, fromNode); - } - | (`Array(_type), `Array(b)) => - b - |> E.A.fmap(fromNodeWithTypeCoercion(evaluationParams, _type)) - |> E.A.R.firstErrorOrOpen - |> E.R.fmap(r => `Array(r)) - | (`Hash(named), `Hash(r)) => - let keyValues = - named - |> E.A.fmap(((name, intendedType)) => - ( - name, - intendedType, - ExpressionTypes.ExpressionTree.Hash.getByName(r, name), - ) - ); - let typedHash = - keyValues - |> E.A.fmap(((name, intendedType, optionNode)) => - switch (optionNode) { - | Some(node) => - fromNodeWithTypeCoercion(evaluationParams, intendedType, node) - |> E.R.fmap(node => (name, node)) - | None => Error("Hash parameter not present in hash.") - } - ) - |> E.A.R.firstErrorOrOpen - |> E.R.fmap(r => `Hash(r)); - typedHash; - | _ => Error("fromNodeWithTypeCoercion error, sorry.") - }; - }; - - let toFloat: typedValue => result(float, string) = - fun - | `Float(x) => Ok(x) - | _ => Error("Not a float"); - - let toArray: typedValue => result(array('a), string) = - fun - | `Array(x) => Ok(x) - | _ => Error("Not an array"); - - let toNamed: typedValue => result(hashTypedValue, string) = - fun - | `Hash(x) => Ok(x) - | _ => Error("Not a named item"); - - let toDist: typedValue => result(node,string) = - fun - | `SamplingDist(`SymbolicDist(c)) => Ok(`SymbolicDist(c)) - | `SamplingDist(`RenderedDist(c)) => Ok(`RenderedDist(c)) - | `RenderedDist(c) => Ok(`RenderedDist(c)) - | `Float(x) => Ok(`SymbolicDist(`Float(x))) - | x => Error("Cannot be converted into a distribution: " ++ toString(x)); -}; - -module Function = { - type t = _function; - type ts = functions; - - module T = { - let make = - (~name, ~inputTypes, ~outputType, ~run, ~shouldCoerceTypes=true, _): t => { - name, - inputTypes, - outputType, - run, - shouldCoerceTypes, - }; - - let _inputLengthCheck = (inputNodes: inputNodes, t: t) => { - let expectedLength = E.A.length(t.inputTypes); - let actualLength = E.A.length(inputNodes); - expectedLength == actualLength - ? Ok(inputNodes) - : Error( - "Wrong number of inputs. Expected" - ++ (expectedLength |> E.I.toString) - ++ ". Got:" - ++ (actualLength |> E.I.toString), - ); - }; - - let _coerceInputNodes = - (evaluationParams, inputTypes, shouldCoerce, inputNodes) => - Belt.Array.zip(inputTypes, inputNodes) - |> E.A.fmap(((def, input)) => - shouldCoerce - ? TypedValue.fromNodeWithTypeCoercion( - evaluationParams, - def, - input, - ) - : TypedValue.fromNode(input) - ) - |> E.A.R.firstErrorOrOpen; - - let inputsToTypedValues = - ( - evaluationParams: ExpressionTypes.ExpressionTree.evaluationParams, - inputNodes: inputNodes, - t: t, - ) => { - _inputLengthCheck(inputNodes, t) - ->E.R.bind( - _coerceInputNodes( - evaluationParams, - t.inputTypes, - t.shouldCoerceTypes, - ), - ) - }; - - let run = - ( - evaluationParams: ExpressionTypes.ExpressionTree.evaluationParams, - inputNodes: inputNodes, - t: t, - ) => { - inputsToTypedValues(evaluationParams, inputNodes, t)->E.R.bind(t.run) - |> ( - fun - | Ok(i) => Ok(i) - | Error(r) => { - Error("Function " ++ t.name ++ " error: " ++ r); - } - ); - }; - }; - - module Ts = { - let findByName = (ts: ts, n: string) => - ts |> Belt.Array.getBy(_, ({name}) => name == n); - - let findByNameAndRun = (ts: ts, n: string, evaluationParams, inputTypes) => - findByName(ts, n) |> E.O.fmap(T.run(evaluationParams, inputTypes)); - }; -}; diff --git a/packages/squiggle-lang/src/distPlus/typeSystem/TypeSystem.res b/packages/squiggle-lang/src/distPlus/typeSystem/TypeSystem.res new file mode 100644 index 00000000..e691ef39 --- /dev/null +++ b/packages/squiggle-lang/src/distPlus/typeSystem/TypeSystem.res @@ -0,0 +1,204 @@ +type node = ExpressionTypes.ExpressionTree.node +let getFloat = ExpressionTypes.ExpressionTree.getFloat + +type samplingDist = [ + | #SymbolicDist(SymbolicTypes.symbolicDist) + | #RenderedDist(DistTypes.shape) +] + +type rec hashType = array<(string, _type)> +and _type = [ + | #Float + | #SamplingDistribution + | #RenderedDistribution + | #Array(_type) + | #Hash(hashType) +] + +type rec hashTypedValue = array<(string, typedValue)> +and typedValue = [ + | #Float(float) + | #RenderedDist(DistTypes.shape) + | #SamplingDist(samplingDist) + | #Array(array) + | #Hash(hashTypedValue) +] + +type _function = { + name: string, + inputTypes: array<_type>, + outputType: _type, + run: array => result, + shouldCoerceTypes: bool, +} + +type functions = array<_function> +type inputNodes = array + +module TypedValue = { + let rec toString: typedValue => string = x => + switch x { + | #SamplingDist(_) => "[sampling dist]" + | #RenderedDist(_) => "[rendered Shape]" + | #Float(f) => "Float: " ++ Js.Float.toString(f) + | #Array(a) => "[" ++ ((a |> E.A.fmap(toString) |> Js.String.concatMany(_, ",")) ++ "]") + | #Hash(v) => + "{" ++ + ((v + |> E.A.fmap(((name, value)) => name ++ (":" ++ toString(value))) + |> Js.String.concatMany(_, ",")) ++ + "}") + } + + let rec fromNode = (node: node): result => + switch node { + | #SymbolicDist(#Float(r)) => Ok(#Float(r)) + | #SymbolicDist(s) => Ok(#SamplingDist(#SymbolicDist(s))) + | #RenderedDist(s) => Ok(#RenderedDist(s)) + | #Array(r) => r |> E.A.fmap(fromNode) |> E.A.R.firstErrorOrOpen |> E.R.fmap(r => #Array(r)) + | #Hash(hash) => + hash + |> E.A.fmap(((name, t)) => fromNode(t) |> E.R.fmap(r => (name, r))) + |> E.A.R.firstErrorOrOpen + |> E.R.fmap(r => #Hash(r)) + | e => Error("Wrong type: " ++ ExpressionTreeBasic.toString(e)) + } + + // todo: Arrays and hashes + let rec fromNodeWithTypeCoercion = (evaluationParams, _type: _type, node) => + switch (_type, node) { + | (#Float, _) => + switch getFloat(node) { + | Some(a) => Ok(#Float(a)) + | _ => Error("Type Error: Expected float.") + } + | (#SamplingDistribution, _) => + PTypes.SamplingDistribution.renderIfIsNotSamplingDistribution( + evaluationParams, + node, + ) |> E.R.bind(_, fromNode) + | (#RenderedDistribution, _) => + ExpressionTypes.ExpressionTree.Render.render(evaluationParams, node) |> E.R.bind(_, fromNode) + | (#Array(_type), #Array(b)) => + b + |> E.A.fmap(fromNodeWithTypeCoercion(evaluationParams, _type)) + |> E.A.R.firstErrorOrOpen + |> E.R.fmap(r => #Array(r)) + | (#Hash(named), #Hash(r)) => + let keyValues = + named |> E.A.fmap(((name, intendedType)) => ( + name, + intendedType, + ExpressionTypes.ExpressionTree.Hash.getByName(r, name), + )) + let typedHash = + keyValues + |> E.A.fmap(((name, intendedType, optionNode)) => + switch optionNode { + | Some(node) => + fromNodeWithTypeCoercion(evaluationParams, intendedType, node) |> E.R.fmap(node => ( + name, + node, + )) + | None => Error("Hash parameter not present in hash.") + } + ) + |> E.A.R.firstErrorOrOpen + |> E.R.fmap(r => #Hash(r)) + typedHash + | _ => Error("fromNodeWithTypeCoercion error, sorry.") + } + + let toFloat: typedValue => result = x => + switch x { + | #Float(x) => Ok(x) + | _ => Error("Not a float") + } + + let toArray: typedValue => result, string> = x => + switch x { + | #Array(x) => Ok(x) + | _ => Error("Not an array") + } + + let toNamed: typedValue => result = x => + switch x { + | #Hash(x) => Ok(x) + | _ => Error("Not a named item") + } + + let toDist: typedValue => result = x => + switch x { + | #SamplingDist(#SymbolicDist(c)) => Ok(#SymbolicDist(c)) + | #SamplingDist(#RenderedDist(c)) => Ok(#RenderedDist(c)) + | #RenderedDist(c) => Ok(#RenderedDist(c)) + | #Float(x) => Ok(#SymbolicDist(#Float(x))) + | x => Error("Cannot be converted into a distribution: " ++ toString(x)) + } +} + +module Function = { + type t = _function + type ts = functions + + module T = { + let make = (~name, ~inputTypes, ~outputType, ~run, ~shouldCoerceTypes=true, _): t => { + name: name, + inputTypes: inputTypes, + outputType: outputType, + run: run, + shouldCoerceTypes: shouldCoerceTypes, + } + + let _inputLengthCheck = (inputNodes: inputNodes, t: t) => { + let expectedLength = E.A.length(t.inputTypes) + let actualLength = E.A.length(inputNodes) + expectedLength == actualLength + ? Ok(inputNodes) + : Error( + "Wrong number of inputs. Expected" ++ + ((expectedLength |> E.I.toString) ++ + (". Got:" ++ (actualLength |> E.I.toString))), + ) + } + + let _coerceInputNodes = (evaluationParams, inputTypes, shouldCoerce, inputNodes) => + Belt.Array.zip(inputTypes, inputNodes) + |> E.A.fmap(((def, input)) => + shouldCoerce + ? TypedValue.fromNodeWithTypeCoercion(evaluationParams, def, input) + : TypedValue.fromNode(input) + ) + |> E.A.R.firstErrorOrOpen + + let inputsToTypedValues = ( + evaluationParams: ExpressionTypes.ExpressionTree.evaluationParams, + inputNodes: inputNodes, + t: t, + ) => + _inputLengthCheck(inputNodes, t)->E.R.bind( + _coerceInputNodes(evaluationParams, t.inputTypes, t.shouldCoerceTypes), + ) + + let run = ( + evaluationParams: ExpressionTypes.ExpressionTree.evaluationParams, + inputNodes: inputNodes, + t: t, + ) => + inputsToTypedValues(evaluationParams, inputNodes, t)->E.R.bind(t.run) + |> ( + x => + switch x { + | Ok(i) => Ok(i) + | Error(r) => Error("Function " ++ (t.name ++ (" error: " ++ r))) + } + ) + } + + module Ts = { + let findByName = (ts: ts, n: string) => ts |> Belt.Array.getBy(_, ({name}) => name == n) + + let findByNameAndRun = (ts: ts, n: string, evaluationParams, inputTypes) => + findByName(ts, n) |> E.O.fmap(T.run(evaluationParams, inputTypes)) + } +} diff --git a/packages/squiggle-lang/src/distPlus/utility/Jstat2.res b/packages/squiggle-lang/src/distPlus/utility/Jstat2.res deleted file mode 100644 index f5df95bd..00000000 --- a/packages/squiggle-lang/src/distPlus/utility/Jstat2.res +++ /dev/null @@ -1,3 +0,0 @@ -@module("jStat") @scope("normal") external mean: (float, float) => float = "mean" - -let foo = mean; \ No newline at end of file