From 54f8b10a9577a376cfd67e35f425c957f37248ae Mon Sep 17 00:00:00 2001 From: Umur Ozkul Date: Wed, 27 Apr 2022 22:00:42 +0200 Subject: [PATCH] Reducer: Environment (Give environement to all function dispatches) - closes #169 --- .../__tests__/Reducer/Reducer_TestHelpers.res | 13 +- .../src/rescript/Reducer/Reducer.res | 7 +- .../src/rescript/Reducer/Reducer.resi | 39 +++--- .../Reducer_Dispatch_BuiltIn.res | 6 +- .../Reducer_Dispatch_BuiltInMacros.res | 5 +- .../Reducer_Expression/Reducer_Expression.res | 114 +++++++++--------- .../ReducerInterface_ExpressionValue.res | 8 ++ .../ReducerInterface_ExternalLibrary.res | 4 +- .../ReducerInterface_GenericDistribution.res | 6 +- .../ReducerInterface_GenericDistribution.resi | 2 +- 10 files changed, 119 insertions(+), 85 deletions(-) diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_TestHelpers.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_TestHelpers.res index 581f6c8a..c56d4130 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_TestHelpers.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_TestHelpers.res @@ -1,9 +1,17 @@ module Expression = Reducer.Expression module ExpressionValue = ReducerInterface.ExpressionValue +module ErrorValue = Reducer_ErrorValue open Jest open Expect +let unwrapRecord = rValue => + rValue->Belt.Result.flatMap(value => switch value { + | ExpressionValue.EvRecord(aRecord) => Ok(aRecord) + | _ => ErrorValue.RETodo("TODO: External bindings must be returned")->Error + } +) + let expectParseToBe = (expr: string, answer: string) => Reducer.parse(expr)->Expression.toStringResult->expect->toBe(answer) @@ -17,7 +25,7 @@ let expectEvalToBe = (expr: string, answer: string) => Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toBe(answer) let expectEvalBindingsToBe = (expr: string, bindings: Reducer.externalBindings, answer: string) => - Reducer.evaluateUsingExternalBindings(expr, bindings) + Reducer.evaluateUsingOptions(expr, ~externalBindings=Some(bindings), ~isPartial=None, ~environment=None) ->ExpressionValue.toStringResult ->expect ->toBe(answer) @@ -27,7 +35,8 @@ let expectEvalPartialBindingsToBe = ( bindings: Reducer.externalBindings, answer: string, ) => - Reducer.evaluatePartialUsingExternalBindings(expr, bindings) + Reducer.evaluateUsingOptions(expr, ~externalBindings=Some(bindings), ~isPartial=Some(true), ~environment=None) + ->unwrapRecord ->ExpressionValue.toStringResultRecord ->expect ->toBe(answer) diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer.res index 63e1f3a7..a403ccd3 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer.res @@ -5,11 +5,12 @@ module Extra = Reducer_Extra module Js = Reducer_Js module MathJs = Reducer_MathJs +type environment = ReducerInterface_ExpressionValue.environment +type errorValue = Reducer_ErrorValue.errorValue type expressionValue = ReducerInterface_ExpressionValue.expressionValue type externalBindings = ReducerInterface_ExpressionValue.externalBindings -let evaluate = Expression.eval -let evaluateUsingExternalBindings = Expression.evalUsingExternalBindings -let evaluatePartialUsingExternalBindings = Expression.evalPartialUsingExternalBindings +let evaluate = Expression.evaluate +let evaluateUsingOptions = Expression.evaluateUsingOptions let parse = Expression.parse let parseOuter = Expression.parseOuter let parsePartial = Expression.parsePartial diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer.resi b/packages/squiggle-lang/src/rescript/Reducer/Reducer.resi index 8bbfc0b5..d71f9a5a 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer.resi +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer.resi @@ -5,22 +5,29 @@ module Extra = Reducer_Extra module Js = Reducer_Js module MathJs = Reducer_MathJs -@genType +@genType +type environment = ReducerInterface_ExpressionValue.environment +@genType +type errorValue = Reducer_ErrorValue.errorValue +@genType type expressionValue = ReducerInterface_ExpressionValue.expressionValue -@genType +@genType type externalBindings = ReducerInterface_ExpressionValue.externalBindings -@genType -let evaluate: string => result -@genType -let evaluateUsingExternalBindings: ( + +@genType +let evaluateUsingOptions: ( + ~environment: option< + QuriSquiggleLang.ReducerInterface_ExpressionValue.environment, + >, + ~externalBindings: option< + QuriSquiggleLang.ReducerInterface_ExpressionValue.externalBindings, + >, + ~isPartial: option, string, - externalBindings, -) => result -@genType -let evaluatePartialUsingExternalBindings: ( - string, - externalBindings, -) => result -let parse: string => result -let parseOuter: string => result -let parsePartial: string => result +) => result +@genType +let evaluate: string => result + +let parse: string => result +let parseOuter: string => result +let parsePartial: string => result diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltIn.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltIn.res index 3e365fd4..3733d7e2 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltIn.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltIn.res @@ -11,7 +11,7 @@ open Reducer_ErrorValue exception TestRescriptException -let callInternal = (call: functionCall): result<'b, errorValue> => { +let callInternal = (call: functionCall, _environment): result<'b, errorValue> => { let callMathJs = (call: functionCall): result<'b, errorValue> => switch call { | ("javascriptraise", [msg]) => Js.Exn.raiseError(toString(msg)) // For Tests @@ -85,12 +85,12 @@ let callInternal = (call: functionCall): result<'b, errorValue> => { /* Reducer uses Result monad while reducing expressions */ -let dispatch = (call: functionCall): result => +let dispatch = (call: functionCall, environment): result => try { let (fn, args) = call // There is a bug that prevents string match in patterns // So we have to recreate a copy of the string - ExternalLibrary.dispatch((Js.String.make(fn), args), callInternal) + ExternalLibrary.dispatch((Js.String.make(fn), args), environment, callInternal) } catch { | Js.Exn.Error(obj) => REJavaScriptExn(Js.Exn.message(obj), Js.Exn.name(obj))->Error | _ => RETodo("unhandled rescript exception")->Error diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltInMacros.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltInMacros.res index 5facc9e7..185897e9 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltInMacros.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltInMacros.res @@ -10,10 +10,12 @@ module Result = Belt.Result open Reducer_ErrorValue type expression = ExpressionT.expression +type environment = ExpressionValue.environment type reducerFn = ( expression, ExpressionT.bindings, + environment, ) => result let rec replaceSymbols = (expression: expression, bindings: ExpressionT.bindings): result< @@ -101,6 +103,7 @@ let rec replaceSymbols = (expression: expression, bindings: ExpressionT.bindings let dispatchMacroCall = ( list: list, bindings: ExpressionT.bindings, + environment, reduceExpression: reducerFn, ): result => { let doBindStatement = (statement: expression, bindings: ExpressionT.bindings) => { @@ -114,7 +117,7 @@ let dispatchMacroCall = ( let rNewValue = rNewExpressionToReduce->Result.flatMap(newExpressionToReduce => - reduceExpression(newExpressionToReduce, bindings) + reduceExpression(newExpressionToReduce, bindings, environment) ) let rNewExpression = rNewValue->Result.map(newValue => ExpressionT.EValue(newValue)) diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression.res index d27ece78..052648ed 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression.res @@ -8,9 +8,12 @@ module T = Reducer_Expression_T open Reducer_ErrorValue +type environment = ReducerInterface_ExpressionValue.environment +type errorValue = Reducer_ErrorValue.errorValue type expression = T.expression -type expressionValue = ExpressionValue.expressionValue -type internalCode = ExpressionValue.internalCode +type expressionValue = ReducerInterface_ExpressionValue.expressionValue +type externalBindings = ReducerInterface_ExpressionValue.externalBindings +type internalCode = ReducerInterface_ExpressionValue.internalCode type t = expression external castExpressionToInternalCode: expression => internalCode = "%identity" @@ -57,19 +60,20 @@ let defaultBindings: T.bindings = Belt.Map.String.empty /* Recursively evaluate/reduce the expression (Lisp AST) */ -let rec reduceExpression = (expression: t, bindings: T.bindings): result => { +let rec reduceExpression = (expression: t, bindings: T.bindings, environment: environment): result => { /* Macros are like functions but instead of taking values as parameters, they take expressions as parameters and return a new expression. Macros are used to define language building blocks. They are like Lisp macros. */ - let doMacroCall = (list: list, bindings: T.bindings): result => - Reducer_Dispatch_BuiltInMacros.dispatchMacroCall(list, bindings, reduceExpression) + let doMacroCall = (list: list, bindings: T.bindings, environment: environment): result => + Reducer_Dispatch_BuiltInMacros.dispatchMacroCall(list, bindings, environment, reduceExpression) let applyParametersToLambda = ( internal: internalCode, parameters: array, args: list, + environment ): result => { let expr = castInternalCodeToExpression(internal) let parameterList = parameters->Belt.List.fromArray @@ -81,22 +85,22 @@ let rec reduceExpression = (expression: t, bindings: T.bindings): result): result => + let reduceValueList = (valueList: list, environment): result => switch valueList { - | list{EvCall(fName), ...args} => (fName, args->Belt.List.toArray)->BuiltIn.dispatch + | list{EvCall(fName), ...args} => (fName, args->Belt.List.toArray)->BuiltIn.dispatch(environment) // "(lambda(x=>internal) param)" | list{EvLambda((parameters, internal)), ...args} => - applyParametersToLambda(internal, parameters, args) + applyParametersToLambda(internal, parameters, args, environment) | _ => valueList->Belt.List.toArray->ExpressionValue.EvArray->Ok } - let rec seekMacros = (expression: t, bindings: T.bindings): result => + let rec seekMacros = (expression: t, bindings: T.bindings, environment): result => switch expression { | T.EValue(_value) => expression->Ok | T.EBindings(_value) => expression->Ok @@ -108,17 +112,17 @@ let rec reduceExpression = (expression: t, bindings: T.bindings): result racc->Result.flatMap(acc => { each - ->seekMacros(bindings) + ->seekMacros(bindings, environment) ->Result.flatMap(newNode => { acc->Belt.List.add(newNode)->Ok }) }) ) - racc->Result.flatMap(acc => acc->doMacroCall(bindings)) + racc->Result.flatMap(acc => acc->doMacroCall(bindings, environment)) } } - let rec reduceExpandedExpression = (expression: t): result => + let rec reduceExpandedExpression = (expression: t, environment): result => switch expression { | T.EList(list{T.EValue(EvCall("$lambda")), T.EParameters(parameters), functionDefinition}) => EvLambda((parameters, functionDefinition->castExpressionToInternalCode))->Ok @@ -130,36 +134,36 @@ let rec reduceExpression = (expression: t, bindings: T.bindings): result racc->Result.flatMap(acc => { each - ->reduceExpandedExpression + ->reduceExpandedExpression(environment) ->Result.flatMap(newNode => { acc->Belt.List.add(newNode)->Ok }) }) ) - racc->Result.flatMap(acc => acc->reduceValueList) + racc->Result.flatMap(acc => acc->reduceValueList(environment)) } | EBindings(_bindings) => RETodo("Error: Bindings cannot be reduced to values")->Error | EParameters(_parameters) => RETodo("Error: Lambda Parameters cannot be reduced to values")->Error } - let rExpandedExpression: result = expression->seekMacros(bindings) + let rExpandedExpression: result = expression->seekMacros(bindings, environment) rExpandedExpression->Result.flatMap(expandedExpression => - expandedExpression->reduceExpandedExpression + expandedExpression->reduceExpandedExpression(environment) ) } -let evalUsingExternalBindingsExpression_ = (aExpression, bindings): result => - reduceExpression(aExpression, bindings) +let evalUsingExternalBindingsExpression_ = (aExpression, bindings, environment): result => + reduceExpression(aExpression, bindings, environment) /* Evaluates MathJs code via Reducer using bindings and answers the result. When bindings are used, the code is a partial code as if it is cut from a larger code. Therefore all statements are assignments. */ -let evalPartialUsingExternalBindings_ = (codeText: string, bindings: T.bindings) => { +let evalPartial_ = (codeText: string, bindings: T.bindings, environment: environment) => { parsePartial(codeText)->Result.flatMap(expression => - expression->evalUsingExternalBindingsExpression_(bindings) + expression->evalUsingExternalBindingsExpression_(bindings, environment) ) } @@ -168,23 +172,12 @@ let evalPartialUsingExternalBindings_ = (codeText: string, bindings: T.bindings) When bindings are used, the code is a partial code as if it is cut from a larger code. Therefore all statments are assignments. */ -let evalOuterWBindings_ = (codeText: string, bindings: T.bindings) => { +let evalOuter_ = (codeText: string, bindings: T.bindings, environment: environment) => { parseOuter(codeText)->Result.flatMap(expression => - expression->evalUsingExternalBindingsExpression_(bindings) + expression->evalUsingExternalBindingsExpression_(bindings, environment) ) } -/* - Evaluates MathJs code and bindings via Reducer and answers the result -*/ -let eval = (codeText: string) => { - parse(codeText)->Result.flatMap(expression => - expression->evalUsingExternalBindingsExpression_(defaultBindings) - ) -} - -type externalBindings = ReducerInterface.ExpressionValue.externalBindings //Js.Dict.t - let externalBindingsToBindings = (externalBindings: externalBindings): T.bindings => { let keys = Js.Dict.keys(externalBindings) keys->Belt.Array.reduce(defaultBindings, (acc, key) => { @@ -192,28 +185,41 @@ let externalBindingsToBindings = (externalBindings: externalBindings): T.binding acc->Belt.Map.String.set(key, T.EValue(value)) }) } -/* - Evaluates code with external bindings. External bindings are a record of expression values. -*/ -let evalUsingExternalBindings = (code: string, externalBindings: externalBindings) => { - let bindings = externalBindings->externalBindingsToBindings - evalOuterWBindings_(code, bindings) + +let evaluateUsingOptions = ( + ~environment: option, + ~externalBindings: option, + ~isPartial: option, + code: string): result => { + + let anEnvironment = switch environment { + | Some(env) => env + | None => ReducerInterface_ExpressionValue.defaultEnvironment + } + + let anExternalBindings = switch externalBindings { + | Some(bindings) => bindings + | None => ReducerInterface_ExpressionValue.defaultExternalBindings + } + + let anIsPartial = switch isPartial { + | Some(isPartial) => isPartial + | None => false + } + + let bindings = anExternalBindings->externalBindingsToBindings + + if anIsPartial { + evalPartial_(code, bindings, anEnvironment) + } else { + evalOuter_(code, bindings, anEnvironment) + } } /* - Evaluates code with external bindings. External bindings are a record of expression values. - The code is a partial code as if it is cut from a larger code. Therefore all statments are assignments. + Evaluates MathJs code and bindings via Reducer and answers the result */ -let evalPartialUsingExternalBindings = (code: string, externalBindings: externalBindings): result< - externalBindings, - 'e, -> => { - let bindings = externalBindings->externalBindingsToBindings - let answer = evalPartialUsingExternalBindings_(code, bindings) - answer->Result.flatMap(answer => - switch answer { - | EvRecord(aRecord) => Ok(aRecord) - | _ => RETodo("TODO: External bindings must be returned")->Error - } - ) +let evaluate = (code: string): result => { + evaluateUsingOptions(~environment=None, ~externalBindings=None, ~isPartial=None, code) } +let eval = evaluate diff --git a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res index 6c15536b..96068dca 100644 --- a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res +++ b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res @@ -22,6 +22,9 @@ type rec expressionValue = @genType type externalBindings = Js.Dict.t +@genType +let defaultExternalBindings: externalBindings = Js.Dict.empty() + type functionCall = (string, array) @@ -84,3 +87,8 @@ let toStringResultRecord = x => | Ok(a) => `Ok(${toStringRecord(a)})` | Error(m) => `Error(${ErrorValue.errorToString(m)})` } + +@genType +type environment = {dummy: int} +@genType +let defaultEnvironment: environment = {dummy: 0} diff --git a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExternalLibrary.res b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExternalLibrary.res index 84d37d95..fde0faf2 100644 --- a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExternalLibrary.res +++ b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExternalLibrary.res @@ -14,8 +14,8 @@ type expressionValue = ExpressionValue.expressionValue Map external calls of Reducer */ -let dispatch = (call: ExpressionValue.functionCall, chain): result => - ReducerInterface_GenericDistribution.dispatch(call) |> E.O.default(chain(call)) +let dispatch = (call: ExpressionValue.functionCall, environment, chain): result => + ReducerInterface_GenericDistribution.dispatch(call, environment) |> E.O.default(chain(call, environment)) /* If your dispatch is too big you can divide it into smaller dispatches and pass the call so that it gets called finally. diff --git a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.res b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.res index f39dc932..5ed33809 100644 --- a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.res +++ b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.res @@ -180,7 +180,7 @@ module Math = { let e = 2.718281828459 } -let dispatchToGenericOutput = (call: ExpressionValue.functionCall): option< +let dispatchToGenericOutput = (call: ExpressionValue.functionCall, _environment): option< DistributionOperation.outputType, > => { let (fnName, args) = call @@ -266,6 +266,6 @@ let genericOutputToReducerValue = (o: DistributionOperation.outputType): result< | GenDistError(err) => Error(REDistributionError(err)) } -let dispatch = call => { - dispatchToGenericOutput(call)->E.O2.fmap(genericOutputToReducerValue) +let dispatch = (call, environment) => { + dispatchToGenericOutput(call, environment)->E.O2.fmap(genericOutputToReducerValue) } diff --git a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.resi b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.resi index fc7ebabc..26464d44 100644 --- a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.resi +++ b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.resi @@ -1,3 +1,3 @@ -let dispatch: ReducerInterface_ExpressionValue.functionCall => option< +let dispatch: (ReducerInterface_ExpressionValue.functionCall, ReducerInterface_ExpressionValue.environment) => option< result, >