Builtins

From APIDesign

Jump to: navigation, search

When writing a language like JavaScript or Enso one necessarily ends up writing builtins - basic language operations that cannot be expressed in the language itself, but need to be built into the interpreter/compiler of the language.

Granularity

All languages have concept of builtins, however the different is the granularity. In general the builtins internals should not be visible in the stack trace. Not only it makes the stack traces needlessly long, but it also complicates debugging as one has to step thru all these builtin levels.

For example Java and JavaScript do the same thing quite differently. In JavaScript all the builtins are written in other language than JavaScript. As such the stacktraces are sane:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].filter (x => { 
  if (x == 5) throw "Error:"+x; 
})

yields (when executed by GraalVM.js, but also any other engine):

$ graalvm-17/bin/js f.js 
Error:5
        at <js> :=>(f.js:2:70-77)
        at <js> :program(f.js:1-3:0-81)

However compare the same example written in Java:

class F {
  public static void main(String... args) {
    java.util.List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12).stream().filter(x -> {
      if (x == 5) throw new RuntimeException("Error:" + x);
      return true;
    }).toList();
  }
}

and such an example yields:

$ graalvm-17/bin/java F.java
Exception in thread "main" java.lang.RuntimeException: Error:5
        at F.lambda$main$0(F.java:4)
        at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:178)
        at java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:720)
        at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
        at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
        at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575)
        at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260)
        at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616)
        at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622)
        at java.base/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627)
        at F.main(F.java:6)

Eleven stacktraces lines instead of two! A lot of boiler plate output instead of two important lines that matter. Who cares there is some AbstractPipeline? Or ReferencePipeline?

Moreover everything gets really complicated when it comes to debugging. In Java the debugger has to step thru all these intermediate stacktraces. That's not the case of JavaScript - in JavaScript the Step Next action goes directly from main code to the body of the lambda function and back. JVM's decision to implement basic operations like filter in Java itself and not as builtin complicates this all. Some IDEs are even sad to had to design a special stream debugger exactly for these purposes.

Implications for Enso Language Designers

Thus, when designing Enso we should ask: Do we want Enso to be like JavaScript or like Java?

Personal tools
buy