Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Targeting Clojure & ClojureScript from a Single Codebase

Targeting Clojure & ClojureScript from a Single Codebase

Talk video available @ https://www.youtube.com/watch?v=Ah9p8cqkTOg

Clojure and ClojureScript have been cast from the same mold, to the extent that it is possible to write programs (or, parts of programs) that are entirely portable without changes. However, their lineage and target runtimes necessitate some differences, and others arise because of decisions made in ClojureScript informed by the experiences of building and using Clojure for years prior. While relatively minor, these differences can provoke significant pain when building applications targeting both languages / runtimes, including forcing the maintenance of duplicate codebases, and splitting up namespaces and projects to localize language- or runtime-specific code.

cljx will be presented as a solution to this difficulty, an implementation of "feature expressions" similar to that found in other lisps. Many applications and libraries have used cljx over the past year to eliminate duplication in their cross-language codebases without impacting downstream users (they only ever see "regular" Clojure and ClojureScript), and without sacrificing development-time conveniences (e.g. cljx's integration with nREPL allows you to load code into either Clojure or ClojureScript REPLs as-is, with the necessary transformations applied on the fly). Real-world usage of cljx in the community will be exhibited, and its use in Clojure and ClojureScript REPLs will be demonstrated.

Chas Emerick

March 25, 2014
Tweet

More Decks by Chas Emerick

Other Decks in Programming

Transcript

  1. Chas Emerick
    @cemerick
    ClojureWest
    March 25, 2014
    Targeting Clojure & ClojureScript
    Targeting Clojure & ClojureScript
    from a Single Codebase
    from a Single Codebase

    View Slide

  2. Writing portable Clojure[Script]:
    Writing portable Clojure[Script]:
    Why?
    Why?
    ● Validation
    ● JavaScript environments are useful for
    general-purpose computation
    – node.js
    – Browsers: OpenGL, IndexedDB, WebRTC, typed arrays,
    web workers, etc…
    ● JavaScript environments can go where the JVM
    can't

    View Slide

  3. A Brief History of ClojureScript
    A Brief History of ClojureScript
    ● First commit 3+ years after Clojure became
    public
    ● Implemented assuming protocols
    – Simpler, easier set of base abstractions
    ● Portability not an objective, so lots of oh-so-close
    naming differences
    – cljs.core vs. clojure.lang
    – INamed vs. Named
    – c.l.IPersistentCollection → #{ICollection
    ICounted IEmptyableCollection}

    View Slide

  4. ClojureScript's material differences
    ClojureScript's material differences
    ● Hybrid compilation model
    – Macros written and evaluated in Clojure
    – :require-macros, :use-macros
    ● No runtime namespaces
    ● JavaScript host
    – “missing” primitives
    – Different, less capable execution model
    – All the wat you could want

    View Slide

  5. Writing portable Clojure[Script]:
    Writing portable Clojure[Script]:
    Options & Problems
    Options & Problems
    ● Don't
    – Maintain separate codebases
    ● Carefully keep portable and non-portable code
    separate
    – Single non-portable call/type/name wags the namespace dog
    – Lowest common denominator
    – lein-cljsbuild “crossovers”
    ● Write code once, translate it for each compilation
    target

    View Slide

  6. Preprocessors in 60 seconds
    Preprocessors in 60 seconds
    Preprocessor
    Source files
    Compilation
    environment
    Env-suitable
    sources
    Compiler

    View Slide

  7. #ifdef SHARC
    millis = sharc_dtime();
    #else if defined(VxWorks) && defined(X86)
    millis = read_386_counter();
    #else if defined(VxWorks)
    millis = read_moto_counter();
    #else if defined(WIN_32)
    millis = read_windows_timer();
    #endif
    30 seconds to go…
    30 seconds to go…

    View Slide

  8. Feature expressions in LISP
    Feature expressions in LISP
    (cons
    #+Lispm "x"
    #+Spice "y"
    #-(or Lispm Spice) "z"
    x)
    When `Lispm` is defined: (CONS "x" X)
    When `Spice` is defined: (CONS "y" X)
    When neither are defined: (CONS "z" X)

    View Slide

  9. cljx: feature expressions for
    cljx: feature expressions for
    Clojure[Script]
    Clojure[Script]
    ● Operates entirely outside of (before) the reading,
    macroexpansion, and compilation of
    Clojure[Script]
    ● Transformation rules applied to a lossless
    representation of Clojure[Script] source code
    (Christophe Grand's sjacket)
    ● Downstream dependencies only ever see the
    generated Clojure or ClojureScript code; cljx
    doesn't leak out of your project
    ● Available via Leiningen and in the (n)REPL

    View Slide

  10. cljx
    .cljx sources
    project.clj config,
    nREPL session type
    ClojureScript
    sources
    ClojureScript
    Compiler
    Clojure
    sources
    Clojure
    Compiler

    View Slide

  11. cljx example
    cljx example
    (ns cemerick.pprng
    #+cljs (:require math.seedrandom
    [cljs.core :as lang])
    #+clj (:require [clojure.core :as lang])
    #+clj (:import java.util.Random)
    (:refer-clojure :exclude (double float
    int long boolean)))

    View Slide

  12. Transformation targeting Clojure
    Transformation targeting Clojure
    (ns cemerick.pprng
    (:require math.seedrandom
    [cljs.core :as lang])
    (:refer-clojure :exclude (double float
    int long boolean)))

    View Slide

  13. Transformation targeting
    Transformation targeting
    ClojureScript
    ClojureScript
    (ns cemerick.pprng
    (:require [clojure.core :as lang])
    (:import java.util.Random)
    (:refer-clojure :exclude (double float
    int long boolean)))

    View Slide

  14. (n)REPL support
    (n)REPL support
    ● Problem: want to be able to load/eval cljx code
    in a REPL session
    ● Problem': want to be able to load cljx files — if
    they exist — that correspond to namespaces
    interactively loaded via ns, require,
    load-namespace, etc.
    ● Solution: cljx nREPL middleware hooks
    clojure.core/load + ClojureScript's
    – This is safer than it sounds

    View Slide

  15. cljx in the wild: notable projects
    cljx in the wild: notable projects
    schema
    formative
    pprng
    sablano
    double-check
    garden
    hickory
    validateur
    frak
    cellular
    enoki
    inflections-clj
    pathetic

    View Slide

  16. cljx challenges and opportunities
    cljx challenges and opportunities
    ● Macros
    ● Line noise
    ● Feature expression flexibility: custom
    transformations
    ● #lang-style syntax control (viz. Racket)

    View Slide

  17. Resources
    Resources
    ● cljx: https://github.com/lynaghk/cljx
    ● “Official” wiki re: feature expressions in Clojure[Script]:
    http://dev.clojure.org/display/design/Feature+Expressions
    ● Common Lisp hyperspec, feature expressions:
    http://www.lispworks.com/documentation/lw50/CLHS/Body/24_aba.htm
    Thank you!
    Thank you!
    Chas Emerick
    http://cemerick.com
    http://twitter.com/cemerick
    http://quilt.org

    View Slide