Migrate to Applicative Functors

This commit is contained in:
Sam Nolan 2022-10-11 17:05:37 +11:00
parent c060304161
commit 8d612f75f0
3 changed files with 125 additions and 78 deletions

View File

@ -2,7 +2,8 @@ open Jest
open Reducer_TestHelpers
describe("Plot Library", () => {
testEvalToBe(`Plot.dist({
testEvalToBe(
`Plot.dist({
show: [{
name: "normal",
value: normal(0, 1)
@ -13,5 +14,7 @@ describe("Plot Library", () => {
name: "constant",
value: 3
}]
})`, "Ok(Plot showing normal,lognormal,constant)")
})`,
"Ok(Plot showing normal,lognormal,constant)",
)
})

View File

@ -3,77 +3,130 @@ open FunctionRegistry_Helpers
let nameSpace = "Plot"
module Internals = {
let parseString = (a: Reducer_T.value): result<string, SqError.Message.t> => {
switch a {
| IEvString(s) => Ok(s)
| _ => Error(SqError.Message.REOther("Expected to be a string"))
module FnApp = {
type fnApp<'a> = {
result: Reducer_T.value => result<'a, SqError.Message.t>,
typeRequired: frType,
}
let fmap = (f: 'a => 'b, m: fnApp<'a>): fnApp<'b> => {
{
result: (a: Reducer_T.value) => E.R.fmap(f, m.result(a)),
typeRequired: m.typeRequired,
}
}
let parseDistributionOrNumber = (a: Reducer_T.value): result<
GenericDist.t,
SqError.Message.t,
> => {
module Record = {
type t<'a> = {
result: Reducer_T.map => result<'a, SqError.Message.t>,
typesRequired: array<(string, frType)>,
}
let getField = (key: string, parser: fnApp<'a>): t<'a> => {
let func = (a: Reducer_T.map) =>
switch Belt.Map.String.get(a, key) {
| Some(x) => parser.result(x)
| None => Error(impossibleError)
}
{result: func, typesRequired: [(key, parser.typeRequired)]}
}
let merge = (m1: t<'a>, m2: t<'b>): t<('a, 'b)> => {
{
result: (a: Reducer_T.map) => E.R.merge(m1.result(a), m2.result(a)),
typesRequired: Belt.Array.concat(m1.typesRequired, m2.typesRequired),
}
}
let fmap = (f: 'a => 'b, m: t<'a>): t<'b> => {
{
result: (a: Reducer_T.map) => E.R.fmap(f, m.result(a)),
typesRequired: m.typesRequired,
}
}
let app = (m1: t<'a => 'b>, m2: t<'a>): t<'b> => {
{
result: (a: Reducer_T.map) =>
E.R.merge(m1.result(a), m2.result(a))->E.R2.fmap(((f, x)) => f(x)),
typesRequired: Belt.Array.concat(m1.typesRequired, m2.typesRequired),
}
}
}
let getString: fnApp<string> = {
let func = (a: Reducer_T.value) =>
switch a {
| IEvString(s) => Ok(s)
| _ => Error(impossibleError)
}
{result: func, typeRequired: FRTypeString}
}
let getArray = (child: fnApp<'a>): fnApp<array<'a>> => {
let func = (a: Reducer_T.value) =>
switch a {
| IEvArray(x) => x->E.A2.fmap(child.result)->E.A.R.firstErrorOrOpen
| _ => Error(impossibleError)
}
{result: func, typeRequired: FRTypeArray(child.typeRequired)}
}
let getRecord = (recMonad: Record.t<'a>): fnApp<'a> => {
let func = (a: Reducer_T.value) =>
switch a {
| IEvRecord(s) => recMonad.result(s)
| _ => Error(impossibleError)
}
{result: func, typeRequired: FRTypeRecord(recMonad.typesRequired)}
}
let getDistOrNumber: fnApp<GenericDist.t> = {
let func = (a: Reducer_T.value) =>
switch a {
| IEvDistribution(s) => Ok(s)
| IEvNumber(s) => Ok(GenericDist.fromFloat(s))
| _ => Error(SqError.Message.REOther("Expected to be a distribution"))
| _ => Error(impossibleError)
}
{result: func, typeRequired: FRTypeDistOrNumber}
}
let parseArray = (
parser: Reducer_T.value => result<'a, SqError.Message.t>,
a: Reducer_T.value,
): result<array<'a>, SqError.Message.t> => {
switch a {
| IEvArray(x) => x->E.A2.fmap(parser)->E.A.R.firstErrorOrOpen
| _ => Error(SqError.Message.REOther("Expected to be an array"))
}
let oneArgDef = (
name: string,
arg1: fnApp<'a>,
def: 'a => result<Reducer_T.value, SqError.Message.t>,
): FnDefinition.t =>
FnDefinition.make(
~name,
~inputs=[arg1.typeRequired],
~run=(inputs, _, _) => {
E.R.bind(arg1.result(inputs[0]), def)
},
(),
)
}
module Internals = {
let makeLabeledDistribution = (
name: string,
distribution: GenericDist.t,
): Reducer_T.labeledDistribution => {name: name, distribution: distribution}
let getLabeledDistribution: FnApp.fnApp<Reducer_T.labeledDistribution> = {
makeLabeledDistribution
->FnApp.Record.fmap(FnApp.Record.getField("name", FnApp.getString))
->FnApp.Record.app(FnApp.Record.getField("value", FnApp.getDistOrNumber))
->FnApp.getRecord
}
let parseRecord = (
parser: Reducer_T.map => result<'b, SqError.Message.t>,
a: Reducer_T.value,
): result<'b, SqError.Message.t> => {
switch a {
| IEvRecord(x) => parser(x)
| _ => Error(SqError.Message.REOther("Expected to be an array"))
}
let makePlot = (show: array<Reducer_T.labeledDistribution>): Reducer_T.plotValue => {
distributions: show,
}
let parseField = (
a: Reducer_T.map,
key: string,
parser: Reducer_T.value => result<'a, SqError.Message.t>,
): result<'a, SqError.Message.t> => {
switch Belt.Map.String.get(a, key) {
| Some(x) => parser(x)
| None => Error(SqError.Message.REOther("expected field " ++ key ++ " in plot dictionary."))
let parsePlotValue: FnApp.fnApp<Reducer_T.plotValue> = {
makePlot
->FnApp.Record.fmap(FnApp.Record.getField("show", FnApp.getArray(getLabeledDistribution)))
->FnApp.getRecord
}
}
let parseLabeledDistribution = (a: Reducer_T.map): result<
Reducer_T.labeledDistribution,
SqError.Message.t,
> => {
let name = parseField(a, "name", parseString)
let distribution = parseField(a, "value", parseDistributionOrNumber)
switch E.R.merge(name, distribution) {
| Ok(name, distribution) => Ok({name: name, distribution: distribution})
| Error(err) => Error(err)
}
}
let parsePlotValue = (a: Reducer_T.map): result<Reducer_T.plotValue, SqError.Message.t> => {
parseField(a, "show", parseArray(parseRecord(parseLabeledDistribution)))->E.R2.fmap(dists => {
let plot: Reducer_T.plotValue = {distributions: dists}
plot
})
}
let dist = (a: Reducer_T.map): result<Reducer_T.value, SqError.Message.t> =>
E.R2.fmap(parsePlotValue(a), x => Reducer_T.IEvPlot(x))
}
let library = [
@ -86,17 +139,7 @@ let library = [
`Plot.dist({show: [{name: "Control", value: 1 to 2}, {name: "Treatment", value: 1.5 to 2.5}]}) `,
],
~definitions=[
FnDefinition.make(
~name="dist",
~inputs=[FRTypeDict(FRTypeAny)],
~run=(inputs, _, _) => {
switch inputs {
| [IEvRecord(plot)] => Internals.dist(plot)
| _ => impossibleError->Error
}
},
(),
),
FnApp.oneArgDef("dist", Internals.parsePlotValue, (a: Reducer_T.plotValue) => Ok(IEvPlot(a))),
],
(),
),

View File

@ -1,4 +1,5 @@
type internalExpressionValueType = Reducer_Value.internalExpressionValueType
let valueTypeToString = Reducer_Value.valueTypeToString
type errorMessage = SqError.Message.t
/*