diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Type/Reducer_Type_switch_replacement_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Type/Reducer_Type_switch_replacement_test.res new file mode 100644 index 00000000..a439bb3c --- /dev/null +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Type/Reducer_Type_switch_replacement_test.res @@ -0,0 +1,123 @@ +open Jest +open Expect + +module DispatchT = Reducer_Dispatch_T +module Expression = Reducer_Expression +module ExpressionT = Reducer_Expression_T +module TypeCompile = Reducer_Type_Compile +module TypeChecker = Reducer_Type_TypeChecker +open ReducerInterface_InternalExpressionValue + +type errorValue = Reducer_ErrorValue.errorValue + +// Let's build a function to replace switch statements +// In dispatchChainPiece, we execute an return the result of execution if there is a type match. +// Otherwise we return None so that the call chain can continue. +// So we want to build a function like +// dispatchChainPiece = (call: functionCall, environment): option> + +// Now lets make the dispatchChainPiece itself. +// Note that I am not passing the reducer to the dispatchChainPiece as an argument because it is in the context anyway. +// Keep in mind that reducerFn is necessary for map/reduce so dispatchChainPiece should have a reducerFn in context. + +let makeMyDispatchChainPiece = (reducer: ExpressionT.reducerFn): DispatchT.dispatchChainPiece => { + // Let's have a pure implementations + module Implementation = { + let stringConcat = (a: string, b: string): string => Js.String2.concat(a, b) + let arrayConcat = ( + a: Js.Array2.t, + b: Js.Array2.t, + ): Js.Array2.t => Js.Array2.concat(a, b) + let plot = _r => "yey, plotted" + } + + let extractStringString = args => + switch args { + | [IEvString(a), IEvString(b)] => (a, b) + | _ => raise(Reducer_Exception.ImpossibleException("extractStringString developer error")) + } + + let extractArrayArray = args => + switch args { + | [IEvArray(a), IEvArray(b)] => (a, b) + | _ => raise(Reducer_Exception.ImpossibleException("extractArrayArray developer error")) + } + + // Let's bridge the pure implementation to expression values + module Bridge = { + let stringConcat: DispatchT.genericIEvFunction = (args, _environment) => { + let (a, b) = extractStringString(args) + Implementation.stringConcat(a, b)->IEvString->Ok + } + let arrayConcat: DispatchT.genericIEvFunction = (args, _environment) => { + let (a, b) = extractArrayArray(args) + Implementation.arrayConcat(a, b)->IEvArray->Ok + } + let plot: DispatchT.genericIEvFunction = (args, _environment) => { + switch args { + // Just assume that we are doing the business of extracting and converting the deep record + | [IEvRecord(_)] => Implementation.plot({"title": "This is a plot"})->IEvString->Ok + | _ => raise(Reducer_Exception.ImpossibleException("plot developer error")) + } + } + } + + // concat functions are to illustrate polymoprhism. And the plot function is to illustrate complex types + let jumpTable = [ + ( + "concat", + TypeCompile.fromTypeExpressionExn("string=>string=>string", reducer), + Bridge.stringConcat, + ), + ( + "concat", + TypeCompile.fromTypeExpressionExn("[any]=>[any]=>[any]", reducer), + Bridge.arrayConcat, + ), + ( + "plot", + TypeCompile.fromTypeExpressionExn( + // Nested complex types are available + // records {property: type} + // arrays [type] + // tuples [type, type] + // <- type contracts are available naturally and they become part of dispatching + // Here we are not enumerating the possibilities because type checking has a dedicated test + "{title: string, line: {width: number, color: string}}=>string", + reducer, + ), + Bridge.plot, + ), + ] + + //Here we are creating a dispatchChainPiece function that will do the actual dispatch from the jumpTable + Reducer_Dispatch_ChainPiece.makeFromTypes(jumpTable) +} + +// And finally, let's write a library dispatch for our external library +// Exactly the same as the one used in real life +let _dispatch = ( + call: functionCall, + environment, + reducer: Reducer_Expression_T.reducerFn, + chain, +): result => { + let dispatchChainPiece = makeMyDispatchChainPiece(reducer) + dispatchChainPiece(call, environment)->E.O2.default(chain(call, environment, reducer)) +} + +// What is important about this implementation? +// A) Exactly the same function jump table can be used to create type guarded lambda functions +// Guarded lambda functions will be the basis of the next version of Squiggle +// B) Complicated recursive record types are not a problem. + +describe("Type Dispatch", () => { + let reducerFn = Expression.reduceExpression + let dispatchChainPiece = makeMyDispatchChainPiece(reducerFn) + test("stringConcat", () => { + let call: functionCall = ("concat", [IEvString("hello"), IEvString("world")]) + + let result = dispatchChainPiece(call, defaultEnvironment) + expect(result)->toEqual(Some(Ok(IEvString("helloworld")))) + }) +}) diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_ChainPiece.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_ChainPiece.res new file mode 100644 index 00000000..6cebfef5 --- /dev/null +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_ChainPiece.res @@ -0,0 +1,19 @@ +module TypeChecker = Reducer_Type_TypeChecker +module T = Reducer_Dispatch_T +open ReducerInterface_InternalExpressionValue + +type errorValue = Reducer_ErrorValue.errorValue + +let makeFromTypes = jumpTable => { + let dispatchChainPiece: T.dispatchChainPiece = ((fnName, fnArgs): functionCall, environment) => { + let jumpTableEntry = jumpTable->Js.Array2.find(elem => { + let (candidName, candidType, _) = elem + candidName == fnName && TypeChecker.checkITypeArgumentsBool(candidType, fnArgs) + }) + switch jumpTableEntry { + | Some((_, _, bridgeFn)) => bridgeFn(fnArgs, environment)->Some + | _ => None + } + } + dispatchChainPiece +} diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_T.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_T.res new file mode 100644 index 00000000..f6234976 --- /dev/null +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_T.res @@ -0,0 +1,20 @@ +module InternalExpressionValue = ReducerInterface_InternalExpressionValue +module ExpressionT = Reducer_Expression_T + +// Each piece of the dispatch chain computes the result or returns None so that the chain can continue +type dispatchChainPiece = ( + InternalExpressionValue.functionCall, + InternalExpressionValue.environment, +) => option> + +type dispatchChainPieceWithReducer = ( + InternalExpressionValue.functionCall, + InternalExpressionValue.environment, + ExpressionT.reducerFn, +) => option> + +// This is a switch statement case implementation: get the arguments and compute the result +type genericIEvFunction = ( + array, + InternalExpressionValue.environment, +) => result diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_Compile.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_Compile.res index 2119ee62..896f4a12 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_Compile.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_Compile.res @@ -38,3 +38,12 @@ let fromTypeExpression = ( (reducerFn: ExpressionT.reducerFn), )->Belt.Result.map(T.fromIEvValue) } + +let fromTypeExpressionExn = ( + typeExpressionSourceCode: string, + reducerFn: ExpressionT.reducerFn, +): T.t => + switch fromTypeExpression(typeExpressionSourceCode, reducerFn) { + | Ok(value) => value + | _ => `Cannot compile ${typeExpressionSourceCode}`->Reducer_Exception.ImpossibleException->raise + } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_TypeChecker.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_TypeChecker.res index e4336df5..fa118079 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_TypeChecker.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_TypeChecker.res @@ -149,6 +149,13 @@ let checkITypeArguments = (anIType: T.iType, args: array): bool => { + switch checkITypeArguments(anIType, args) { + | Ok(_) => true + | _ => false + } +} + let checkArguments = ( typeExpressionSourceCode: string, args: array, diff --git a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_StdLib.res b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_StdLib.res index 6c133332..ec6c4fd4 100644 --- a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_StdLib.res +++ b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_StdLib.res @@ -1,6 +1,7 @@ module Bindings = Reducer_Bindings -let internalStdLib = Bindings.emptyBindings->SquiggleLibrary_Math.makeBindings->SquiggleLibrary_Versions.makeBindings +let internalStdLib = + Bindings.emptyBindings->SquiggleLibrary_Math.makeBindings->SquiggleLibrary_Versions.makeBindings @genType let externalStdLib = internalStdLib->Bindings.toTypeScriptBindings