Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reducer real life type checking example to build a library #900

Merged
merged 5 commits into from
Aug 12, 2022

Conversation

umuro
Copy link
Contributor

@umuro umuro commented Jul 28, 2022

This is a real life example/guide/inspiration of how to replace switch statements with official function types.

A complete dispatch function is implemented in a unit test. Providing an example of deep record checking (plot).

Also "any" type is added for now so that complete module libraries can be written in this fashion.

Better see the colored version: https://github.com/quantified-uncertainty/squiggle/pull/900/files#diff-22fa54f9384977cbf9012a9de088d6ed9df7974725e8dfd635163261280ef4ec

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<internalExpressionValue>,
      b: Js.Array2.t<internalExpressionValue>,
    ): Js.Array2.t<internalExpressionValue> => 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<internalExpressionValue, 'e> => {
  let dispatchChainPiece = makeMyDispatchChainPiece(reducer)
  dispatchChainPiece(call, environment)->E.O2.defaultFn(()=>chain(call, environment, reducer))
}

If all the functions are typed then switching to the fully typed version of Squiggle will be smoother.
It's clear that in real life we need a bundle of extract functions to drill into records and arrays. Not provided yet.

Note: I later found out that E.O2.default(chain... should not be used because it calls the chain every time unnecessarily.

Umur Ozkul added 3 commits July 28, 2022 13:42
any will be depreciated after the implementation of binding type
variables
@umuro umuro requested a review from OAGr as a code owner July 28, 2022 20:32
@netlify
Copy link

netlify bot commented Jul 28, 2022

Deploy Preview for squiggle-components ready!

Name Link
🔨 Latest commit c6eb696
🔍 Latest deploy log https://app.netlify.com/sites/squiggle-components/deploys/62e359e10fb11e0008149a14
😎 Deploy Preview https://deploy-preview-900--squiggle-components.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@netlify
Copy link

netlify bot commented Jul 28, 2022

Deploy Preview for squiggle-documentation ready!

Name Link
🔨 Latest commit c6eb696
🔍 Latest deploy log https://app.netlify.com/sites/squiggle-documentation/deploys/62e359e1f9a72b00086f5b64
😎 Deploy Preview https://deploy-preview-900--squiggle-documentation.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@codecov-commenter
Copy link

codecov-commenter commented Jul 28, 2022

Codecov Report

Merging #900 (c6eb696) into develop (fb0aa72) will decrease coverage by 0.11%.
The diff coverage is 83.33%.

@@             Coverage Diff             @@
##           develop     #900      +/-   ##
===========================================
- Coverage    57.96%   57.85%   -0.12%     
===========================================
  Files           73       74       +1     
  Lines         4094     4114      +20     
===========================================
+ Hits          2373     2380       +7     
- Misses        1721     1734      +13     
Impacted Files Coverage Δ
...t/Distributions/PointSetDist/MixedShapeBuilder.res 54.54% <0.00%> (-18.19%) ⬇️
...ript/Reducer/Reducer_Type/Reducer_Type_Compile.res 78.57% <66.66%> (-3.25%) ⬇️
...r/Reducer_Dispatch/Reducer_Dispatch_ChainPiece.res 85.71% <85.71%> (ø)
.../Reducer/Reducer_Type/Reducer_Type_TypeChecker.res 83.33% <90.00%> (ø)
...rescript/Distributions/PointSetDist/Continuous.res 62.22% <100.00%> (ø)
...c/rescript/Distributions/PointSetDist/Discrete.res 23.65% <100.00%> (ø)
...ucerInterface/ReducerInterface_ExternalLibrary.res 100.00% <100.00%> (ø)
...cript/ReducerInterface/ReducerInterface_StdLib.res 100.00% <100.00%> (ø)
packages/squiggle-lang/src/rescript/Utility/E.res 48.29% <100.00%> (+0.54%) ⬆️
...rface/ReducerInterface_InternalExpressionValue.res 46.87% <0.00%> (-5.00%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update e13fe62...c6eb696. Read the comment docs.

@umuro
Copy link
Contributor Author

umuro commented Jul 29, 2022

@OAGr I have a strong feeling that we need lenses to extract data from deep nested types/records: https://github.com/Astrocoders/lenses-ppx
Unlike simple types, without lenses, accessing deep structures can create deep and messy code. We might experiment on finishing the plot example with lenses.

@OAGr
Copy link
Contributor

OAGr commented Jul 29, 2022

@OAGr I have a strong feeling that we need lenses to extract data from deep nested types/records: https://github.com/Astrocoders/lenses-ppx

I like lenses, but am pretty weary of adding PPX. The rescript people recommend fairly strongly against them.
https://forum.rescript-lang.org/t/can-you-demistify-rescript-ppx-support/2149

"If you are new to ReScript and plan to write some production code (instead of just playing it), you are not encouraged to investing your time in PPX or PPX based technologies."

If we do add them, it would be good to really try to restrict their use, so that if they break later on, we're not in too bad a state.

@umuro
Copy link
Contributor Author

umuro commented Jul 29, 2022

I see your point... Due to the nature of expression values, there might be a custom solution.
I am thinking about writing functions with ImpossibleException (see the code) to drill deep values. They are type checked anyway so silencing not checked alternatives is a good hack.

@umuro
Copy link
Contributor Author

umuro commented Aug 8, 2022

@OAGr Curious about your comments

Copy link
Contributor

@OAGr OAGr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just looked through this. It took me a while (to get to it and mainly understand it).

Overall, I like it. It seems simple enough, should be easy to incorporate with the Function Registry code. I left one comment, but generally happy to merge.

@OAGr OAGr mentioned this pull request Aug 11, 2022
@umuro
Copy link
Contributor Author

umuro commented Aug 11, 2022

As an example, it's good for merging.
I will enhance the record example after making type aliases later. But that's another matter.

@OAGr OAGr merged commit 5dd988a into develop Aug 12, 2022
@OAGr OAGr deleted the reducer-typecheck-example branch August 12, 2022 00:45
@umuro umuro mentioned this pull request Aug 15, 2022
26 tasks
@OAGr OAGr mentioned this pull request Sep 14, 2022
@OAGr OAGr mentioned this pull request Oct 12, 2022
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants