Jim Lynch Codes
  • Blog
  • About Jim

Writings about one coder's stories & experiences.

Loading Loose Clojure & ClojureScript Files From A REPL

12/7/2017

0 Comments

 
The beauty of Clojure's non-OOP style is that the functions are not tied to some blueprint that needs to be instantiated. Instead, once they are included, required, or loaded, etc. the functions are just there, able to be called  at some later point in the code. Because of this, Idiomatic Clojure lends itself well to functions that are pure and can be called in complete isolation. It should be noted that load-file works for both Clojure and ClojureScript!  

REPLs Galore!

In the world of Clojure, REPLs are everything. Luckily, there are lots of REPLS to choose from such as nREPL in Leiningen or Boot for good ole' JVM Clojure and Leiningen, Lumo, Plank, or Klipse for ClojureScript. This isn't a guide on setting up a REPL, but just know that load-file should work for any REPL, on both Clojure and Clojurescript!

Loading A Source File

Suppose you have typed a single function into a Clojure or ClojureScript file (with a clj or cljs file extension, respectively). I'm going to save the snippet below as derp.cljs and use lumo, but you could just as well do this with a cljs file and leiningen. 
(defn derpy []
  42)

(defn derpmatic [n]
  (* n 100))
All this code does is defin a function named derpy​ that, when called, returns the number 42. Now, let's look at calling it. I'll boot up lumo by typing the simple lumo command into my shell.
lumo
Alright then. Now that your REPL is started, it's time to get down to the magical goodness of load-file!
(load-file "derp.cljs")
;; => (returns nothing)
It's a big worrying if you're used to instant feedback. load-file is the kind of thing where it just gives you an empty prompt if it worked, or some type of "Could not load file" error if it didn't. Now that we've loaded the file containing our functions, let's call them!
(derpy)
;; =>  42

(derpmatic 20)
;; => 2000
Huzzah!! For today is a glorious day because we are able to use a REPL to evaluate Clojure functions! But wait... there's more!

Load Files In Different Folders

Now let's say that you want to load a file from some random directory. Welp, you can just enter the path to the file in the string that you provide to load-file. Note that the path is relative to the directory you were in when you started the REPL. 
(load-file "./../derp-folder/derp.cljs")

Load-File With Namespaces

In a "real life" Clojure or ClojureScript project you won't often see files with loose functions defined in them like the files here. For the most part each file has a namespace declared at the top of the file, and this makes the fuctions scoped to that namespace. This is important because you just call the functions like this anymore when you load the file. Instead, you need to either :refer certain functions or explicitly use a library alias when calling the functions. To demonstrate what I might let;s look at an example. Let's create a new file and named and write in it the snippet of code below.  
(ns derpskies.derpspace)

(defn derpstring []
  (str "d" "e" "r" "p"))
Now back in our RELP we can load the nampespace, but we can
t just call straight up (derpstring).
(load-file "./my-derpstring.cljs")
(derpstring)

;; => WARNING: Use of undeclared Var cljs.user/derpstring at ; line 1 
;; Cannot read property 'call' of undefined
;;          (evalmachine.<anonymous>:1:21)
;;          ContextifyScript.Script.runInThisContext (vm.cljs:44:33)
;;          Object.runInThisContext (vm.cljs:116:38)
;;          (Object.lt)
;;          (Object.lumo.repl.caching_node_eval)
;;          (NO_SOURCE_FILE <embedded>:6020:273)
;;          z (NO_SOURCE_FILE <embedded>:6021:263)
;;          Object.cljs.js.eval_str_STAR_ (NO_SOURCE_FILE <;; embedded>:6022:328)
;;          Function.cljs.js.eval_str.cljs$core$IFn$_invoke$arity$5 (NO_SOURCE_FILE <embedded>:6025:508);; 
;;          Object.lumo.repl.execute_text (NO_SOURCE_FILE <embedded>:6511:475)
The above code gives us that error, but we CAN call derpstring with the code below.
(derpskies.derpspace/derpstring)
;; => "derp"
Ah yes, there's our cute little derp string. Although this works, it's quite annoying to have to write out this huge namespace every time you want to call a function. I mean good golly, such finger energy wasted! It should be a crime! One thing we can do is cal l"in-ns" which allows us to call functions from that namespace without writing out the full path.
(load-file "derpstring.cljs")
(in-ns 'derpskies.derpspace)
(derpstring)
;; => "derp"
Another thing we can do is alias the namespace so that we can give it a shorter name. Note: alias is onoly a function in JVM Clojure, not ClojureScript. ​
(load-file "clojure-derp.clj")
;; => #'derpskies.derpspace/derpstring

(clojure.core/alias 'd 'derpskies.derpspace)
;; => nil

(d/derpstring)
;; => "derp"
Yep, load-file is a great tool to keep in your trusty Clojure knapsack, but now let's take a look at require!

Using Require From The REPL

You will probably see require more often that load-file, to be honest. This is because require (or with a namespace declaration, :require) is a function that you can use in a project with multiple files whereas load-file is strictly meant to be called at a REPL. Let's take a look at how we can use require with our cute little ClojureScript file. We'll first go back to the no-namespace derp.cljs file from the beginning of this post. If you want to use require to load a file that has no namespace, don't fret; you can do it! Just navigate into the folder that contains the file, load up your repl, and call require on the name of your file (no string quotes, just a single quote in front).
(require 'derp)
;; => nil

(derpy)
;; => 42
Please note that only ClojureScript will allow you to require a file with no namespace. JVM Clojure should throw an error.
Alright, so what if we DO have a namespace in our file. What do we do then? Well, we simply require the namespace and the Clojure compiler will find the file on its own! Well, it's not really finding the file on its own; you're telling it how to find it! You see, when you use require the namespace must match the path to the file. If you use "." in the namespace this signifies drilling down into a folder. In the example below, you must name the file "gg.cljs" or "gg.clj" for it to work!
(ns gg)

(defn fall-down []
  "ouch!")
Now let's suppose you create a folder named "root" and one inside of it called "derpskies". You place this file, gg.cls, inside of the derpskies folder and in your command shell navigate into the root folder. Let's change our file to reflect this new namespace.
(ns derpskies.gg)

(defn fall-down []
  "ouch!")
With this we can require and call the functions like so:
(require 'derpskies.gg)
;; => nil

(derpskies.gg/fall-down)
;; => "ouch!"
That works, but it's rather wordy. Let's give an alias to our library with the :as macro.
(require 'gg)
;; => nil

(gg/fall-down)
;; => "ouch!"
(require '[derpskies.gg :as g])
;; => nil

(g/fall-down)
;; => "ouch!"
This above snippet shows about how to call a function from a nice, short alias. How lovely. But it get's even better! Suppose you have a big old file with loads of functions, but you only need one? Well, you can import just these functions and tell Clojure that you are referring to the functions from that specific file or library.
(require '[derpskies.gg :refer [fall-down]])
;; => nil

(fall-down)
;; => "ouch!"

REPL Driven Development

In the Clojure there's the concept of REPL driven development. I think it's partly mocking the "X-driven development" buzzwords that are popular in the software development world right now. Functional languages that emphasize pure functions, and especially Clojure with it's independence from OOP, often are great for being able to leverage a REPL. When's the last time you tested your JavaScript app by just loading up the chrome dev console and calling the function? Normally people don't because their JavaScript functions are full of side-effects, dependencies on other files, local state, etc. Clojure functions are often independent of other parts of your project, and in addition to just being good coding practice it gives you a lot more opportunity to load functions into your REPL so that you can test them out in complete isolation. In fact, it's almost like manually doing unit tests! I'm very excited to be using Clojure more now and have this instant-feedback kind of language where everything is very decoupled and modular, and you should be too!
0 Comments

Your comment will be posted after it is approved.


Leave a Reply.

    ​Author

    Picture
    The posts on this site are written and maintained by Jim Lynch. About Jim...
    Picture
    Follow @JimLynchCodes
    Follow @JimLynchCodes

    Categories

    All
    Actionscript 3
    Angular
    AngularJS
    Automated Testing
    AWS Lambda
    Behavior Driven Development
    Blockchain
    Blogging
    Business Building
    C#
    C / C++
    ClojureScript / Clojure
    Coding
    Community Service
    CS Philosophy
    Css / Scss
    Dev Ops
    Firebase
    Fitness
    Flash
    Front End
    Functional Programming
    Git
    Go Lang
    Haskell
    Illustrations
    Investing
    Java
    Javascript
    Lean
    Life
    Linux
    Logic Pro
    Music
    Node.js
    Planning
    Productivity
    Professionalism
    Python
    React
    Redux / Ngrx
    Refactoring
    Reusable Components
    Rust
    Security
    Serverless
    Shell Scripting
    Swift
    Test Driven Development
    Things
    TypeScript
    Useful Sites
    Useful Tools
    Video
    Website Development
    WebStorm
    Writing

    Archives

    March 2023
    August 2021
    February 2021
    January 2021
    October 2020
    September 2020
    May 2020
    April 2020
    February 2020
    January 2020
    December 2019
    October 2019
    September 2019
    August 2019
    July 2019
    June 2019
    May 2019
    April 2019
    March 2019
    February 2019
    January 2019
    December 2018
    November 2018
    October 2018
    September 2018
    August 2018
    June 2018
    May 2018
    April 2018
    March 2018
    February 2018
    January 2018
    December 2017
    November 2017
    October 2017
    September 2017
    August 2017
    July 2017
    May 2017
    April 2017
    March 2017
    February 2017
    January 2017
    December 2016
    November 2016
    October 2016
    September 2016
    August 2016
    July 2016
    June 2016
    May 2016
    April 2016
    March 2016
    February 2016
    January 2016
    December 2015
    November 2015
    October 2015

    RSS Feed

  • Blog
  • About Jim
JimLynchCodes © 2023