UNPKG

wisp

Version:

Homoiconic JS with clojure syntax, s-expressions & macros

779 lines (556 loc) 20.5 kB
# Language Essentials ## Data structures #### nil `nil` is just like JavaScript `undefined` with the difference that it cannot be redefined. It compiles down to `void(0)` in JavaScript. ```clojure nil ; => void(0) ``` #### Booleans `true` / `false` are directly equivalent to plain JavaScript booleans: ```clojure true ; => true ``` #### Numbers _wisp_ numbers are directly equivalent to JavaScript numbers: ```clojure 1 ; => 1 ``` #### Strings _wisp_ strings are JavaScript strings: ```clojure "Hello world" ``` ...and can be multi-line: ```clojure "Hello, My name is wisp!" ``` #### Characters Characters are syntactic sugar for single character strings: ```clojure \a ; => "a" \b ; => "b" ``` #### Keywords Keywords are symbolic identifiers that evaluate to themselves: ```clojure :keyword ; => "keyword" ``` Since in JavaScript string constants fulfill the purpose of symbolic identifiers, keywords compile to equivalent strings in JavaScript. This allows using keywords in Clojure(Script) and JavaScript idiomatic fashion: ```clojure (window.addEventListener :load handler false) ``` Keywords can also be invoked as functions, although that too is syntax sugar that compiles to property access in JavaScript: ```clojure (:bar foo) ; => (foo || 0)["bar"] ``` Note that keywords in _wisp_ are not real functions so they can't be composed or passed to high order functions. #### Vectors _wisp_ vectors are plain JavaScript arrays, but nevertheless all standard library functions are non-destructive and pure functional as in Clojure. ```clojure [ 1 2 3 4 ] ``` Note: Commas are considered whitespace and can be used if desired: ```clojure [1, 2, 3, 4] ``` #### Dictionaries _wisp_ does not have Clojure-like value-to-value maps by default, but rather dictionaries that map to plain JavaScript objects. Therefore, unlike Clojure, keys cannot consist of arbitrary types. ```clojure { "foo" bar :beep-bop "bop" 1 2 } ``` Like with vectors, commas are optional but can come handy for separating key value pairs. ```clojure { :a 1, :b 2 } ``` #### Lists What would be a LISP without lists? _wisp_ being homoiconic, its code is made up of lists representing expressions. As in other LISPs, the first item of an expression is an operator or function that takes the remainder of the list as arguments, and compiles accordingly to JavaScript: ```clojure (foo bar baz) ; => foo(bar, baz); ``` The compiled JavaScript is quite unlikely to end up with lists as they primarily serve their purpose at compile time. Nevertheless lists are supported and can be used (more further down) #### Arrays _wisp_ partially emulates Clojure(Script) handling of arrays in two ways: 1. By using `get`, which compiles to guarded access in JavaScript: ```clojure (get [1 2 3] 1) ; => ([1, 2, 3] || 0)[0] ``` 2. By using `aget`, which compiles to unguarded access and can (for the moment) also be used to perform item assignments: ```clojure (aget an-array 2) ; => anArray[2]; (set! (aget an-array 2) "bar") ; => anArray[2] = "bar"; ``` (`aset` will be added ASAP for symmetry, but you can easily define an equivalent macro for the moment) ## Conventions _wisp_ tries very hard to compile to JavaScript that feels hand-crafted while trying to embrace LISP-style idioms and naming conventions, and translates them to equivalent JavaScript conventions: ```clojure (dash-delimited) ; => dashDelimited (predicate?) ; => isPredicate (**privates**) ; => __privates__ (list->vector) ; => listToVector ``` This makes for very natural-looking code, but also allows some things to be expressed in different ways. For instance, the following function invocations will translate to the same things: ```clojure (parse-int x) (parseInt x) (array? x) (isArray x) ``` ## Special forms There are some special operators in _wisp_ in the sense that they compile to JavaScript expressions rather then function calls. Identically-named functions are also available in the standard library to allow function composition. #### Arithmetic operations _wisp_ comes with special forms for common arithmetic: ```clojure (+ a b) ; => a + b (+ a b c) ; => a + b + c (- a b) ; => a - b (* a b c) ; => a * b * c (/ a b) ; => a / b (mod a b) ; => a % 2 ``` #### Comparison operations ...and special forms for common comparisons: ```clojure (identical? a b) ; => a === b (identical? a b c) ; => a === b && b === c (= a b) ; => a == b (= a b c) ; => a == b && b == c (> a b) ; => a > b (>= a b) ; => a >= b (< a b c) ; => a < b && b < c (<= a b c) ; => a <= b && b <= c ``` #### Logical and bitwise operations ...and special forms for logical and bitwise operations: ```clojure (and a b) ; => a && b (and a b c) ; => a && b && c (or a b) ; => a || b (and (or a b) (and c d)) ; (a || b) && (c && d) ``` ```clojure (bit-and a b) ; => a & b (bit-or a b) ; => a | b (bit-xor a b) ; => a ^ b (bit-shift-left a 2) ; => a << 2 (bit-shift-right b 3) ; => b >> 3 (bit-shift-right-zero-fil a 1) ; => a >>> 1 ``` #### Definitions Variable definitions also happen through special forms: ```clojure (def a) ; => var a = void(0); (def b 2) ; => var b = 2; ``` #### Assignments In _wisp_ variables can be set to new values via the `set!` special form. Note that in functional programing binding changes are a bad practice (avoiding these will improve the quality and testability of your code), but there are always cases where this is required for JavaScript interoperability: ```clojure (set! a 1) ; => a = 1 ``` The `!` suffix is a useful visual reminder that you're causing a side-effect. #### Conditionals Conditional code branching in _wisp_ is expressed via the `ìf` special form. As usual, the first expression following `if` is a condition - if it evaluates to `true` the result of the `if` form will be the second expression, otherwise it'll be the third "else" expression: ```clojure (if (< number 10) "Digit" "Number") ``` The third ("else") expression is optional, and if missing and the conditional evaluates to `true` the result will be `nil`. ```clojure (if (monday? today) "How was your weekend") ``` The form `cond` is also available: ```clojure (cond (monday? today) "How was your weekend" (friday? today) "Enjoy your weekend" (weekend? today) "Huzzah weekend" :else "Some other day") ``` Each term is evaluated in sequence until it evaluates to true. If none are true, the form evaluates to `undefined`. #### Combining expressions In _wisp_ everything is an expression, but sometimes one might want to combine multiple expressions into one, usually for the purpose of evaluating expressions that have side-effects. That's where `do` comes in: ```clojure (do (console.log "Computing sum of a & b") (+ a b)) ``` `do` can take any number of expressions (including `0`, in which case it will evaluate to `nil`): ```clojure (do) ; => nil ``` #### Bindings The `let` special form evaluates sub-expressions in a lexical context in which symbols in its binding-forms (first item) are bound to their respective expression results: ```clojure (let [a 1 b (+ a 1)] (+ a b)) ; => 3 ``` #### Functions _wisp_ functions are plain JavaScript functions ```clojure (fn [x] (+ x 1)) ; => function(x) { return x + 1; } ``` _wisp_ functions can have names, just as in JavaScript ```clojure (fn increment [x] (+ x 1)) ; => function increment(x) { return x + 1; } ``` _wisp_ function _declarations_ can also contain documentation and some metadata: ```clojure (defn sum "Return the sum of all arguments" {:version "1.0"} [x] (+ x 1)) ``` Function _expressions, though, can only have names: ```clojure (fn increment {:added "1.0"} [x] (+ x 1)) ``` _Note: Docstrings and metadata are not included in compiled JavaScript yet, but support for that is planned._ #### Arguments _wisp_ makes capturing of remaining (`rest`) arguments a lot easier than JavaScript. An argument that follows an ampersand (`&`) symbol will capture the remaining args in a standard vector (i.e., array). ```clojure (fn [x & rest] (rest.reduce (fn [sum x] (+ sum x)) x)) ``` #### Overloading Functions In _wisp_ functions can be overloaded depending on arity (the number of arguments they take), without introspection of remaining arguments. ```clojure (fn sum "Return the sum of all arguments" {:version "1.0"} ([] 0) ([x] x) ([x y] (+ x y)) ([x & more] (more.reduce (fn [x y] (+ x y)) x))) ``` If a function does not have variadic overload and more arguments are passed to it, it throws an exception. ```clojure (fn ([x] x) ([x y] (- x y))) ``` #### Loops and TCO A classic way to build a loop in LISP is via recursion, _wisp_ provides a `loop` `recur` construct that allows for tail call optimization: ```clojure (loop [x 10] (if (> x 1) (print x) (recur (- x 2)))) ``` ## Other Special Forms ### Instantiation In _wisp_ type instantiation has a concise form, by way of suffixing the function with a period (`.`): ```clojure (Type. options) ``` However, the more verbose but more JavaScript-like form is also valid: ```clojure (new Class options) ``` #### Method calls In _wisp_ method calls are no different from function calls, but prefixed with a period (`.`): ```clojure (.log console "hello wisp") ``` ...and, of course, the more JavaScript-like forms are supported too: ```clojure (window.addEventListener "load" handler false) ``` #### Attribute access In _wisp_, attribute access is also treated like a function call, but attributes need to be prefixed with `.-`: ```clojure (.-location window) ``` Compound properties can be accessed via the `get` special form: ```clojure (get templates (.-id element)) ``` #### Catching Exceptions In _wisp_ exceptions can be handled via the `try` special form. As with everything else, the `try` form is also an expression that evaluates to `nil` if no handling takes place. ```clojure (try (raise exception)) ``` ...the `catch` form can be used to handle exceptions... ```clojure (try (raise exception) (catch error (.log console error))) ``` ...and the `finally` clause can be used too: ```clojure (try (raise exception) (catch error (recover error)) (finally (.log console "That was a close one!"))) ``` #### Throwing Exceptions In a non-idiomatic twist (but largely for symmetry and JavaScript interop), the `throw` special form allows throwing exceptions: ```clojure (fn raise [message] (throw (Error. message))) ``` ## Macros _wisp_ has a powerful programmatic macro system which allows the compiler to be extended by user code. Many core constructs of _wisp_ are in fact normal macros, and you are encouraged to study the source to learn how to build your own. Nevertheless, the following sections are a quick primer on macros. #### quote Before diving into macros too much, we need to learn a few more things. In LISP any expression can be quoted to prevent it from being evaluated. As an example, take the symbol `foo` - by default, you will be evaluating the reference to its corresponding value: ```clojure foo ``` But if you wish to refer to the literal symbol, this is how you do it: ```clojure (quote foo) ``` or, as shorthand: ```clojure 'foo ``` Any expression can be quoted to prevent its evaluation (these are not, however, compiled to JavaScript): ```clojure 'foo ':bar '(a b) ``` #### An Example Macro _wisp_ doesn't have the `unless` special form or a macro, but it's trivial to implement it via macros. But it's useful to try implementing it as a function to understand a use case for macros, so let's get started: `unless` is easy to understand -- we want to execute a `body` unless a given `condition` is `true`: ```clojure (defn unless-fn [condition body] (if condition nil body)) ``` But since function arguments are evaluated before the function itself is called, the following code will _always_ write a log message: ```clojure (unless-fn true (console.log "should not print")) ``` Macros solve this problem, because they do not evaluate their arguments immediately. Instead, you get to choose when (and if!) the arguments to a macro are evaluated. Macros take items of the expression as arguments and return a new form that is compiled instead. ```clojure (defmacro unless [condition form] (list 'if condition nil form)) ``` The body of the `unless` macro executes at macro expansion time, producing an `if` form for compilation. This way the compiled JavaScript is a conditional instead of a function call. ```clojure (unless true (console.log "should not print")) ``` #### syntax-quote Simple macros like the above could be written via templating and expressed as syntax-quoted forms. `syntax-quote` is almost the same as plain `quote`, but it allows sub expressions to be unquoted so that form acts as a template. The symbols inside the form are resolved to help prevent inadvertent symbol capture, which can be done via `unquote` and `unquote-splicing` forms. ```clojure (syntax-quote (foo (unquote bar))) (syntax-quote (foo (unquote bar) (unquote-splicing bazs))) ``` Note that there is special syntactic sugar for both unquoting operators: 1. Syntax quote: Quote the form, but allow internal unquoting so that the form acts as template. Symbols inside the form are resolved to help prevent inadvertent symbol capture. ```clojure `(foo bar) ``` 2. Unquote: Use inside a syntax-quote to substitute an unquoted value. ```clojure `(foo ~bar) ``` 3. Splicing unquote: Use inside a syntax-quote to splice an unquoted list into a template. ```clojure `(foo ~bar ~@bazs) ``` For example, the built-in `defn` macro can be defined with a simple template macro. That's more or less how the built-in `defn` macro is implemented. ```clojure (defmacro define-fn [name & body] `(def ~name (fn ~@body))) ``` Now if we use `define-fn` form above, the defined macro will be expanded at compile time, resulting into different program output. ```clojure (define-fn print [message] (.log console message)) ``` Not all of the macros can be expressed via templating, but all of the language is available to assemble macro expanded forms. #### Another Macro Example As an example, let's define a macro to ease functional chaining, a technique popular in JavaScript but usually expressed via method chaining. A typical use of that would be something like: ```javascript open(target, "keypress"). filter(isEnterKey). map(getInputText). reduce(render) ``` Unfortunately, though, it usually requires that all the chained functions need to be methods of an object, which is very limited and has the undesirable effect of making third party functions "second class". But using macros we can achieve similar chaining without such tradeoffs, and chain _any_ function: ```clojure (defmacro -> [& operations] (reduce (fn [form operation] (cons (first operation) (cons form (rest operation)))) (first operations) (rest operations))) (-> (open target :keypress) (filter enter-key?) (map get-input-text) (reduce render)) ``` ## Import/Export (Symbols and Modules) ### Exporting Symbols All the top level definitions in a file are exported by default: ```clojure (def foo bar) (defn greet [name] (str "hello " name)) ``` ...but it's still possible to define top level bindings without exporting them via `^:private` metadata: ```clojure (def ^:private foo bar) ``` ...and a little syntax sugar for functions: ```clojure (defn- greet [name] (str "hello " name)) ``` ### Importing Module importing is done via an `ns` special form that is manually named. Unlike `ns` in Clojure(Script), _wisp_ takes a minimalistic approach and supports only one essential way of importing modules: ```clojure (ns interactivate.core.main "interactive code editing" (:require [interactivate.host :refer [start-host!]] [fs] [wisp.backend.javascript.writer :as writer] [wisp.sequence :refer [first rest] :rename {first car rest cdr}])) ``` Let's go through the above example to get a complete picture regarding how modules can be imported: 1. The first parameter `interactivate.core.main` is a name of the module / namespace. In this case it represents module `./core/main` under the package `interactivate`. While this is not enforced in any way the common convention is that these mirror the filesystem hierarchy. 2. The second string parameter is just a description of the module and is completely optional. 3. The `(:require ...)` form defines dependencies that will be imported at runtime, and the example above imports multiple modules: 1. First it imports the `start-host!` function from the `interactivate.host` module. That will be loaded from the `../host` location, since because module paths are resolved relative to a name, but only if they share the same root. 2. The second form imports `fs` module and makes it available under the same name. Note that in this case it could have been written without wrapping it in brackets. 3. The third form imports `wisp.backend.javascript.writer` module from `wisp/backend/javascript/writer` and makes it available via the name `writer`. 4. The last and most complex form imports `first` and `rest` functions from the `wisp.sequence` module, although it also renames them and there for makes available under different `car` and `cdr` names. While Clojure has many other kinds of reference forms they are not recognized by _wisp_ and will therefore be ignored. ### Types and Protocols In wisp protocols can be defined same as in Clojure(Script), via [defprotocol](http://clojuredocs.org/clojure_core/clojure.core/defprotocol): ```clojure (defprotocol ISeq (-first [coll]) (-rest [coll])) (defprotocol ICounted (^number count [coll] "constant time count")) ``` Above code will define `ISeq`, `ICounted` protocols (objects representing those protocol) and `_first`, `_rest`, `count` functions, that dispatch on first argument (that must implement associated protocol). Existing types / classes (defined either in wisp or JS) can be extended to implement specific protocol using [extend-type](http://clojuredocs.org/clojure_core/clojure.core/extend-type): ```clojure (extend-type Array ICounted (count [array] (.-length array)) ISeq (-first [array] (aget array 0)) (-rest [array] (.slice array 1))) ``` Once type / class implemnets some protocol, it's functions can be used on the instances of that type / class. ```clojure (count []) ;; => 0 (count [1 2]) ;; => 2 (-first [1 2 3]) ;; => 1 (-rest [1 2 3]) ;; => [2 3] ``` In wisp value can be checked to satisfy given protocol same as in Clojure(Script) via [satisfies?](http://clojuredocs.org/clojure_core/clojure.core/satisfies_q): ```clojure (satisfies? ICounted [1 2]) (satisfies? ISeq []) ``` New types (that translate to JS classes) can be defined same as in Clojure(Script) via [deftype](http://clojuredocs.org/clojure_core/clojure.core/deftype) form: ```clojure (deftype List [head tail size] ICounted (count [_] size) ISeq (-first [_] head) (-rest [_] tail) Object (toString [self] (str "(" (join " " self) ")"))) ``` Note: Protocol functions are defined as methods with unique names (that include namespace info where protocol was defined, protocol name & method name) to avoid name collisions on types / classes implementing them. This implies that such methods aren't very useful from JS side. Special `Object` protocol can be used to define methods who's names will be kept as is, which can be used to define interface to be used from JS side (like `toString` method above). In wisp multiple types can be extended to implement a specific protocol using [extend-protocol](http://clojuredocs.org/clojure_core/clojure.core/extend-protocol) form same as in Clojure(Script) too. [homoiconicity]:http://en.wikipedia.org/wiki/Homoiconicity [clojure]:http://clojure.org/ [macros]:http://clojure.org/macros [s-expressions]:http://en.wikipedia.org/wiki/S-expression [clojurescript]:https://github.com/clojure/clojurescript [markdown]:http://daringfireball.net/projects/markdown/