Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What type checks does Raku perform at compile time? May that change in the future?

Currently (as of August 2020) Rakudo does not typecheck the return values of functions at compile time; that is, it does not provide static guarantees that functions satisfy their return constraints. Concretely, the following two functions both compile as Raku:

sub get-int(--> Int) { 'bug' }
sub get-int($a --> Int) { 
   when $a == 5 { 'Rare bug' }
   default      { 42 }
}

I have two related questions:

  1. Is there any way to know what (if any) typechecking currently takes place at compile time? (Either via a list someone has written, somewhere in the docs, or a central place in the Rakudo source) Or is it more ad hoc than that?

  2. Is this lack of compile time typechecking an intentional design decision? Or is adding more static typechecking something that would be nice to have one day, but just hasn't yet been implemented?

(I'm familiar with Johnathan's great answer to The performance penalties for types/constraints in Raku?, which states that "Raku mandates that type constraints written into the program are enforced at runtime at latest." That answer describes various ways to avoid run-time costs of typechecks, but doesn't describe what, if any, typechecks are done at compile time (which would certainly avoid runtime costs!).)

like image 613
codesections Avatar asked Aug 19 '20 09:08

codesections


People also ask

What are compile time checks?

Compile-time checking occurs during the compile time. Compile time errors are error occurred due to typing mistake, if we do not follow the proper syntax and semantics of any programming language then compile time errors are thrown by the compiler.

Is Raku compiled?

When you load a module for the first time, Rakudo compiles it into bytecode.


1 Answers

Currently very little checking of types is done at compile time; that which is mostly takes place as a side-effect of the static optimizer. The checks today are largely about subroutine calls, where:

  • We can determine the arity of the call and know that the number of passed arguments will never match
  • We have literal arguments and can see they'd never possibly match with the signature

This is a leftover from when the static optimizer did more inlining work. These days, it only inlines native operators at compile time, and leaves the rest for the VM's dynamic optimizer, which is vastly more capable at inlining and can also uninline (permitting speculative optimization, but also meaning original stack traces can be recovered, whereas the static optimizer lost this information).

Doing more at compile time is considered desirable, however there are some practical issues to consider.

  1. Introducing additional checks can also introduce breakage of code that worked before. Consider a module with a code path that would fail a stricter compile time check, but that is being used in systems that never run into that case. If it started failing to compile on newer versions of the compiler, then it would become impossible to deploy that system after a compiler upgrade. In general, this means the checks performed should change on language version changes. (This still means people should declare the language version they are writing against when writing code, mind.)
  2. That more checks being done at compile time will "certainly avoid runtime costs" may be true, but it's not trivial to reason about. A managed runtime cannot blindly trust the promises made in the bytecode it is given, since that could lead to memory safety violations (which lead to SIGSEGV or worse). This is quite clearly true in a language like Raku, where the semantics of type checking are programmable, but it's true on the JVM, CLR, and so forth. The biggest type-related wins in Raku come from the use of native types, which can avoid a lot of allocations and thus garbage collection work.
  3. Implementing further checks will increase the complexity of the compiler and also the amount of time needed for compilation. The first of these is already an issue; the compiler frontend hasn't seen any significant architectural changes in around a decade. The current RakuAST work that lays a foundation for macros also involves a near rewrite of the compiler frontend. The improved architecture should ease implementing further compile-time type checks, but thought is also going into how aspects of compilation might be parallelized, which could allow the compiler to do more without increasing the wallclock compile time.

Once the current compiler frontend overhaul is complete, more compile-time checks being introduced (but only enabled from the next language version) seems quite likely - at least, so long as somebody works on it.

However, there's an even more exciting opportunity coming up in this area: since there will be an API to Raku programs, and with plans coming together for custom compiler passes, it will also soon be possible to implement type checkers as modules! Some of those may lead to checks that make it into future Raku language versions. Others may be quite domain-specific and aimed at enabling more correct use of a given module. Others may enforce rigors that are not in the spirit of the base language, but that some language users might wish to opt in to.

like image 83
Jonathan Worthington Avatar answered Sep 26 '22 17:09

Jonathan Worthington