Phil Schumann, Berlin
 Software Developer

10 Oct 2017 Objective: to understand how an ML-ish-to-imperative/OO code generator (such as —in this example— the PureScript-to-JavaScript transpiler) represents functional type-classes (FP's approximate use-case equivalent to OO's interfaces).

Dissecting PureScript's transpiled type-classes

Objective: to understand how an ML-ish-to-imperative/OO code generator (such as —in this example— the PureScript-to-JavaScript transpiler) represents functional type-classes (FP's approximate use-case equivalent to OO's interfaces).

Minimal PureScript project

src/Mini/ShowCls.purs — we recreate the well-known Show type-class ("interface"), adding a default instance ("implementation") for String:

module Mini.ShowCls where
import Prelude

class MyShow a where
    showMe :: a -> String

instance myShowStr :: MyShow String where
    showMe s =
        "\"" <> s <> "\""

src/Mini/ShowImpl.purs — a custom Email new-type of String and adding its own instance of all ShowMe methods (just showMe, as per above.)

module Mini.ShowImpl where
import Mini.ShowCls

newtype Email = At String

instance showMail :: MyShow Email where
    showMe (At addr) =
        showMe addr

src/Main.purs — user code importing both:

module Main where
import Mini.ShowCls
import Mini.ShowImpl
import Prelude
import Control.Monad.Eff
import Control.Monad.Eff.Console

main :: forall e. Eff (console :: CONSOLE) Unit
main =
    let
        mail :: Email
        mail = At "baz@foo.bar"
        shown = showMe mail
    in do
        log shown

The generated JS code

output/Main/index.js, imports/exports stripped:

var main = (function () {
    var shown = Mini_ShowCls.showMe(Mini_ShowImpl.showMail)("baz@foo.bar");
    return Control_Monad_Eff_Console.log(shown);
})();
  • The At new-type data constructor call has been erased, just the underlying string remains — as is proper for all single-constructor, single-arg (ie. all) new-types.
  • Our generic call to the MyShow type-class' showMe method gets turned into 2 calls:
    1. first one to obtain the applicable concrete method implementation as a function — apparently by passing the imported JS representation object of the custom instance directly to the type-class;
    2. secondly, the actual call to that retrieved concrete "implementing" function.

output/Mini/ShowImpl/index.js

var At = function (x) {
    return x;
};
var showMail = new Mini_ShowCls.MyShow(function (v) {
    return Mini_ShowCls.showMe(Mini_ShowCls.myShowStr)(v);
});
  • The At new-type constructor of Email is still being generated, even though its usage was erased (see above). Not sure why, there's probably a good reason.
  • This exports the custom showMail instance that the transpiler was smart enough to reference explicitly in Main above.
  • We see that such a custom instance is produced by obtaining a representation object from Mini_ShowCls.MyShow(..), passing presumably all concrete "implementing" functions that comprise this instance as constructor arguments.
  • As this representation object is exported, it is imported in Main to be passed back in to ShowCls's generic one-stop showMe exported function.
  • As this just defers to the default showMe instance for String, the compiler figured out (just like above in Main) exactly what underlying concrete function implementation (myShowStr) to pass on to the embedded showMe call.

output/Mini/ShowCls/index.js

var MyShow = function (showMe) {
    this.showMe = showMe;
};
var showMe = function (dict) {
    return dict.showMe;
};
var myShowStr = new MyShow(function (s) {
    return "\"" + (s + "\"");
});
  • As already presumed above, the MyShow type-class turns into a simple constructor of a representation object with named methods, initialized in order from the same-named arguments.
  • We now see how and why all generic showMe calls work, and this function seems utterly superfluous, but probably this way makes it more elegant to code-gen? Well, we haven't touched type-variables and such here at all.
  • The pre-defined default MyShow instance for Strings (namely, myShowStr) is being exported, show-casing the creation of an instance's representation object via the MyCode constructor with the concrete function implementation passed in.
 
Cogent
There are two ways of constructing a software design: one way is to make it so simple that there are obviously no deficiencies, and the other way is make it so complicated that there are no obvious deficiencies. The first method is far more difficult.
Tony Hoare
Sourcery
Working with since • 1998 » BasicPascal1999 » HTMLASP2000 » SQLCSSJSVB2001 » JavaPHP2002 » C#ASP.NET2003 » XSLT / XPath2004 » Prolog2006 » SharePoint2008 » F# • Python • Lisp2011 » Go • WebGL • Node.js • CoffeeScript • 2012 » OpenGLGLSL2015 » Haskell2016 » TypeScriptElm2017 » PureScriptVue.js
Details..
Making-of
Site theme: none; hand-crafted.
Static site gen: HaXtatic.
Icons, logos, fonts: © respective owners.