We needn't preface this discussion with the deficiencies of JavaScript. The sheer number of "Compile-To-JavaScript" languages is evidence that the language has problems and that many desire a suitable alternative. Yet a good replacement should possess a certain set of qualities with which we can justify the added overhead of compilation and integration with existing tools. Here we are going to look at PureScript, a small strongly, statically typed language which compiles to JavaScript1, and discuss why it is an excellent substitute.
module Example where
import Debug.Trace
main = trace "Hello, PureScript!"
JavaScript's syntax has grown quite legacy and fails to elegantly handle many modern programming constructs. To truly add value any potential replacement language must ease this syntactic burden. PureScript does a fine job of this by offering an ML style syntax which is very heavily inspired by Haskell. With PureScript we get clean and simple syntax for modern constructs such higher-order functions which fit snugly within existing JavaScript idioms.
module Example where
import Debug.Trace
main = giveMeATracingFunction trace
giveMeATracingFunction fn = fn "Hello, PureScript!"
Interoperation with existing JavaScript code and tools is another area where PureScript excels. PureScript has a dead simple FFI and compiles to genuinely legible JavaScript that can be easily inspected and debugged using modern browsers or existing JavaScript analysis tools. Furthermore, basic constructs such as records and arrays directly correspond to their JavaScript counterparts which facilitate the passing of data between the two worlds. For additional convenience and clarity, compiled PureScript modules export their public content using the revealing module pattern2, the functions of which can easily be called from JavaScript. If all of that isn't enough, the executable psc_make compiles PureScript into true CommonJS modules for easy integration into existing projects.
module Example where
import Control.Monad.Eff
import Debug.Trace
foreign import traceFn
"function traceFn(statement) { \
\ console.lo(statement); \
\}" :: String -> Eff (trace :: Trace) {}
main = giveMeATracingFunction traceFn
giveMeATracingFunction fn = fn "Hello, PureScript!"
The astute reader might have caught the error in our code:
console.lo
should in fact be console.log
. Unlike PureScript,
JavaScript allows simple typos to turn into run-time
errors. One-hundred percent test coverage would surely catch mistakes
like this but why rely on super-human testing capability when
PureScript can detect a whole class of errors automatically and for
free! Also, notice that PureScript does a good job of preserving the
function names we have chosen. This, coupled with the clean and
readable JavaScript that PureScript produces, gives us the ability to
immediately identify the location of our JavaScript errors in the
original source.
Syntax amenable to modern programming idioms and easy integration with existing JavaScript are both wonderfully important characteristics but as "real world" developers we choose only those tools which give us the ability to produce quality code faster and with more consistency. PureScript's close relationship with Haskell really shines in this respect. The powerful type system catches many errors at compile time that would otherwise be hidden until uncovered by well written tests or by discovery of a defect in the running application. Also, PureScript comes with an interpreter which can be used for quick exploration of ideas and the evaluation independent source files, this creates a tight feedback loop which speeds time to production.