Skip to content

Commit 4bba895

Browse files
committed
Documentation updates #fw1/445
1 parent 417b0e3 commit 4bba895

File tree

2 files changed

+78
-45
lines changed

2 files changed

+78
-45
lines changed

source/documentation/4.0/cfml-and-clojure.markdown

Lines changed: 71 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
layout: page
33
title: "Using Clojure with CFML"
4-
date: 2016-05-27 09:40
4+
date: 2016-09-14 15:00
55
comments: false
66
sharing: false
77
footer: true
@@ -126,14 +126,14 @@ it also generates a test skeleton which you can run like this:
126126
Because the test skeleton doesn't have any valid tests -- just one deliberate failure -- you should see:
127127

128128
lein test myapp.core-test
129-
129+
130130
lein test :only myapp.core-test/a-test
131-
131+
132132
FAIL in (a-test) (core_test.clj:7)
133133
FIXME, I fail.
134134
expected: (= 0 1)
135135
actual: (not (= 0 1))
136-
136+
137137
Ran 1 tests containing 1 assertions.
138138
1 failures, 0 errors.
139139
Tests failed.
@@ -154,20 +154,20 @@ You should go through the [Getting Started](https://github.com/boot-clj/boot#get
154154
This specifies an external dependency (on my `boot-new` task library), then the name of the task (`new`) then the arguments to that task (the `t`ype
155155
of the template and `n`ame of project to create).
156156

157-
If you want to get going quickly and you've already installed **Leiningen** and gone through the project creation and testing steps above, you can just create a `build.boot` file in
157+
If you want to get going quickly and you've already installed **Leiningen** and gone through the project creation and testing steps above, you can just create a `build.boot` file in
158158
that (**Leiningen**-created) project and run code and tests like this:
159159

160160
;; build.boot
161161
(set-env! :resource-paths #{"src"}
162162
:dependencies '[[org.clojure/clojure "1.8.0"]])
163-
163+
164164
(require 'myapp.core)
165165
(deftask run []
166166
(myapp.core/-main))
167167

168-
Like `project.clj` under **Leiningen**, we specify the project dependencies. We also specify where our source code is -- there's no default -- using `:resource-paths`.
169-
Next we load the main namespace of our new application and define a task called `run` that calls the main function in that application. Whilst **Leiningen** has a
170-
built-in task called `run`, you still have to declare the main namespace (or specify it as a command line argument). **Boot** takes the approach that this is
168+
Like `project.clj` under **Leiningen**, we specify the project dependencies. We also specify where our source code is -- there's no default -- using `:resource-paths`.
169+
Next we load the main namespace of our new application and define a task called `run` that calls the main function in that application. Whilst **Leiningen** has a
170+
built-in task called `run`, you still have to declare the main namespace (or specify it as a command line argument). **Boot** takes the approach that this is
171171
"just code" so you can define whatever tasks you want, to run whatever Clojure code you want.
172172

173173
Accordingly, **Boot** does not have a built-in task to run tests, but there are readily available libraries to provide this functionality. We'll update `build.boot` as follows:
@@ -177,9 +177,9 @@ Accordingly, **Boot** does not have a built-in task to run tests, but there are
177177
:source-paths #{"test"}
178178
:dependencies '[[org.clojure/clojure "1.8.0"]
179179
[adzerk/boot-test "1.0.7"]])
180-
180+
181181
(require '[adzerk.boot-test :refer [test]])
182-
182+
183183
(require 'myapp.core)
184184
(deftask run []
185185
(myapp.core/-main))
@@ -226,7 +226,7 @@ files and folders that would see in your `myapp` Clojure test project above:
226226
.gitignore LICENSE README.md doc resources target
227227
boot.properties build.boot project.clj src test
228228

229-
The files shown in the first line are for FW/1. You might later add a `controllers` folder and a `model` folder if you
229+
The files shown in the first line are for FW/1. You might later add a `controllers` folder and a `model` folder if you
230230
write any of those pieces in CFML.
231231

232232
The files shown in the second line are generated by **Leiningen** and you can pretty much ignore them. You'll see that by
@@ -257,9 +257,9 @@ in `hello/controllers/main.clj`.
257257
If you run `lein test` in the `6helloclojure` folder, you'll see:
258258

259259
lein test hello.controllers.main-test
260-
260+
261261
lein test hello.services.greeter-test
262-
262+
263263
Ran 2 tests containing 3 assertions.
264264
0 failures, 0 errors.
265265

@@ -365,7 +365,7 @@ tests. The arrow syntax means "take this thing and pass it through these functio
365365

366366
(-> {} default :greeting)
367367

368-
takes an empty struct and passes it as the first argument to the `default` function (our handler method being tested) and then
368+
takes an empty struct and passes it as the first argument to the `default` function (our handler method being tested) and then
369369
pass the result to the `:greeting` function -- remember that `(:greeting my-struct)` is like `my_struct.greeting` in CFML. So
370370
this tests that if the `rc` is empty and you run `main.default` you get a new key called `:greeting` whose value is `"Hello anonymous!"`.
371371

@@ -515,7 +515,7 @@ or `test` folder, except that `-` in a namespace identifier matches `_` in the f
515515
files but it isn't very common to see that, so don't worry about it.
516516

517517
In the `6helloclojure` example, you'll see `hello.controllers.main` for `src/hello/controllers/main.clj` and `hello.controllers.main-test` for
518-
`test/hello/controllers/main_test.clj`. If you follow this basic convention, you won't go wrong. If your `ns` declaration doesn't match the
518+
`test/hello/controllers/main_test.clj`. If you follow this basic convention, you won't go wrong. If your `ns` declaration doesn't match the
519519
filesystem path, you can get strange errors when you attempt to access it. Think of it much like the dotted-path used to access CFCs in CFML.
520520

521521
The second important part of `ns` is the list of namespaces your code `:require`s. There are two basic forms here:
@@ -587,7 +587,7 @@ The third entry is Clojure's core JDBC library.
587587
The fourth entry is the Clojure language and core functions.
588588

589589
Now run `lein repl` and we can try this out. As the REPL starts up, it will download the new
590-
libraries and then you'll get the prompt. Let's create test database and write and read some
590+
libraries and then you'll get the prompt. Let's create test database and write and read some
591591
data with it:
592592

593593
taskmanager.core> (require '[clojure.java.jdbc :as sql])
@@ -599,7 +599,7 @@ data with it:
599599
"task VARCHAR(32),"
600600
"done BOOLEAN DEFAULT false"
601601
")")])
602-
[0] ;; success! we created the task table
602+
[0] ;; success! we created the task table
603603
taskmanager.core> (sql/insert! db :task {:task "Test database"})
604604
({:1 1M}) ;; the sequence of inserted keys:
605605
;; there is just one key, labeled :1, with the value 1
@@ -669,7 +669,7 @@ In your `taskmanager` Clojure folder, create a `services` subfolder under `src/t
669669

670670
;; src/taskmanager/services/greeting.clj
671671
(ns taskmanager.services.greeting)
672-
672+
673673
(defn hello [name] (str "Hello, " name "!"))
674674

675675
Now we'll update our `main.default` view to look like this:
@@ -689,28 +689,28 @@ We're going to use that as the basis of our service. Let's create `task.clj` in
689689
;; src/taskmanager/services/task.clj
690690
(ns taskmanager.services.task
691691
(:require [clojure.java.jdbc :as sql]))
692-
692+
693693
;; for now we'll just hard-code one database spec but
694694
;; we could pass it in from our controller as needed
695695
(def db {:dbtype "derby" :dbname "cfmltest" :create true})
696-
696+
697697
(defn create-task-table []
698698
(sql/execute! db [(str "CREATE TABLE task ("
699699
"id INT GENERATED ALWAYS AS IDENTITY,"
700700
"task VARCHAR(32),"
701701
"done BOOLEAN DEFAULT false"
702702
")")]))
703-
703+
704704
(defn add-task
705705
"Given a task name, add it to our database and return the new row's ID."
706706
[task]
707707
(-> (sql/insert! db :task {:task task}) first :1))
708-
708+
709709
(defn complete-task
710710
"Given a task ID, mark it as done and return the number of rows updated."
711711
[id]
712712
(-> (sql/update! db :task {:done true} ["id = ?" id]) first))
713-
713+
714714
(defn task-list
715715
"Return the tasks.
716716
If all is true, return all tasks, else just the incomplete ones."
@@ -800,7 +800,7 @@ We'll add a dummy controller so we can test this:
800800
If we hit the app in our browser with `?reload=true` in the URL, you should see:
801801

802802
List All | Add New Task
803-
803+
804804
You have nothing to do!
805805

806806
If you click `Add New Task` and fill in the form and click `Add!`, you'll end up back on this page with no tasks listed.
@@ -926,13 +926,13 @@ Now run this with `?reload=true` in the URL and it should work just as it did be
926926
How do you know it's using the Clojure version? Because `framework.ioclj` adds the Clojure namespaces it finds after any CFCs it finds, overwriting any beans
927927
with the same alias. If you want to convince yourself, remove `controllers/main.cfc` from your `taskmanager` folder in the webroot, and reload the application again.
928928

929-
So why would we write our controllers in Clojure instead of CFML?
929+
So why would we write our controllers in Clojure instead of CFML?
930930

931-
* As simple functions (that take `rc` as input and produce an updated `rc` as output), they're easy to write unit tests for.
931+
* As simple functions (that take `rc` as input and produce an updated `rc` as output), they're easy to write unit tests for.
932932
* You don't need to convert `rc` data structures back and forth between CFML and Clojure since that's taken care of automatically in FW/1 (technically in `cljcontroller.cfc`).
933933
* You can work in the REPL building and testing your entire application's functionality (and then work on the views with a browser).
934934
* We get the full power of Clojure's data abstractions, concurrency, and immutability working for us.
935-
* With your services and controllers in Clojure, you're one step away from building all-Clojure web applications using [FW/1 for Clojure](https://github.com/framework-one/fw1-clj).
935+
* With your services and controllers in Clojure, you're one step away from building all-Clojure web applications using [FW/1 for Clojure](https://github.com/framework-one/fw1-clj).
936936

937937
### RC Value Conversion
938938

@@ -947,6 +947,37 @@ functions:
947947
* `->double` -- Accepts any value, string or numeric, and tries to convert it to a `Double` value. You can provide a second argument which specifies the default to return if conversion fails (otherwise it will return `0.0`).
948948
* `->boolean` -- Accepts any value, boolean, string or numeric, and tries to convert it to a `Boolean` value. You can provide a second argument which specifies the default to return if conversion fails (otherwise it will return `false`). This treats the strings `"true"` and `"yes"` as `true` (not case sensitive), as well as treating non-zero numeric values as `true` (and zero as `false`), just like CFML.
949949

950+
### Augmenting RC With Clojure Expressions
951+
952+
Normally `rc` is automatically converted back and forth between a CFML "struct" and a Clojure hash map so that inside Clojure controllers, the keys are lowercase keywords (as expected in Clojure). This automatic conversion handles "regular" data types just fine but there may be times when you want to pass more complex data into Clojure controllers, such as a `Component` providing the application's configuration or state. The `cljcontroller.cfc` file provides an extension point for this -- `callClojure()` -- to allow you to add Clojure expressions to the request context (after conversion), before calling the actual controller (and then removing them again before returning to CFML). Write a CFC that extends `framework.cljcontroller` and overrides `callClojure()`. Make sure to call `super.callClojure()`. Then tell DI/1 that `cljcontroller` is your CFC instead:
953+
954+
```
955+
// myapp/cljcontroller.cfc:
956+
component extends=framework.cljcontroller {
957+
958+
// add/remove application.clojure_component to the request context
959+
// this is a Clojure Component (Stuart Sierra's "reloaded" workflow)
960+
// and is not convertible to/from CFML -- note that rcClj is a Clojure
961+
// hash map at this point so you must use Clojure functions to manipulate it
962+
function callClojure( string qualifiedName, any rcClj ) {
963+
var core = variables.cfmljure.clojure.core;
964+
var appComponent = core.keyword( "application-component" );
965+
rcClj = core.assoc( rcClj, appComponent, application.clojure_component );
966+
var rawResult = super.callClojure( qualifiedName, rcClj );
967+
return core.dissoc( rawResult, appComponent );
968+
}
969+
970+
}
971+
972+
// framework configuration:
973+
diConfig : {
974+
...
975+
cljcontroller : "myapp.cljcontroller",
976+
...
977+
},
978+
979+
```
980+
950981
# A Clojure Primer
951982

952983
To learn about Clojure in any depth, I'd recommend you go through the **[More Stuff to Read](#more-stuff-to-read)** section at the end of this
@@ -1188,11 +1219,11 @@ Each entry is called a "coordinate" and contains a "group ID" and an "artifact I
11881219
and artifact ID are the same thing (so `clj-time` is shorthand for `clj-time/clj-time`).
11891220

11901221
To search Maven Central for `org.clojure/clojure` you would use the query `g:"org.clojure" AND a:"clojure"` which asks for
1191-
group ID `org.clojure` and artifact ID `clojure`. Right now there are over 90 versions of that library on Maven Central and the
1222+
group ID `org.clojure` and artifact ID `clojure`. Right now there are over 90 versions of that library on Maven Central and the
11921223
latest is `1.9.0-alpha3` but if you click the `All (92)` link, you'll see the most recent non-prerelease version is `1.8.0`
11931224
which is what **Leiningen** puts in `project.clj` by default (in Leiningen 2.6.1 and later).
11941225

1195-
On the other hand, `clj-time` comes from Clojars because it is a community project. If you search for `clj-time` you'll
1226+
On the other hand, `clj-time` comes from Clojars because it is a community project. If you search for `clj-time` you'll
11961227
get a lot of results but most of them are not canonical versions. The most recent canonical version is https://clojars.org/clj-time but
11971228
there are other, earlier canonical versions, such as https://clojars.org/backtype/clj-time so you need to be a bit careful. If in doubt,
11981229
get on IRC, Slack, or the mailing list and ask!
@@ -1219,10 +1250,10 @@ For more detail, consult the [**Boot** web site](http://boot-clj.com).
12191250
# About Functional Programming
12201251

12211252
Functional programming isn't new. It's origins lie in Lisp which was created in the 1950's and is the second-oldest computer language
1222-
(second only to FORTRAN). Throughout the 70's and 80's a lot of functional languages were created, mostly in academia, to study the
1253+
(second only to FORTRAN). Throughout the 70's and 80's a lot of functional languages were created, mostly in academia, to study the
12231254
benefits of the functional style, as well look at levels of expressiveness in programming languages. Classic functional languages
12241255
include Standard ML, Miranda, and Haskell. Haskell was a result of the proliferation of similar functional languages being created
1225-
by each university in England (and elsewhere). It was decided that a single, committee-designed functional language should exist
1256+
by each university in England (and elsewhere). It was decided that a single, committee-designed functional language should exist
12261257
that included the best ideas of all of the diverse variants out there. Haskell is probably the most widely used language today from
12271258
that era. It has an extremely powerful type system and a very strong view of purity -- lack of side effects -- but it has been
12281259
used extensively over the last 25 years in industry as well as academia.
@@ -1258,7 +1289,7 @@ for purely sequential access, a vector for indexed (random) access, and a hash m
12581289
Clojure and Scala share a lot of heavily optimized implementation details in their persistent data structures.
12591290

12601291
Despite all this efficiency, you still need to think about how to use data structures, and there are going to be some algorithms
1261-
where bashing a data structure in place is just going to be faster. It won't be as safe, just faster. Clojure is fine with the
1292+
where bashing a data structure in place is just going to be faster. It won't be as safe, just faster. Clojure is fine with the
12621293
idea of localized mutation and has versions of vectors and hash maps that are optimized for that purpose (known as transients).
12631294

12641295
The most important aspect of these data structures in Clojure is the set of abstractions over them, including "sequence"
@@ -1295,7 +1326,7 @@ Consider these two functions (in CFML):
12951326
writeLog( "Invalid stuff, not saved" );
12961327
}
12971328
}
1298-
1329+
12991330
function saveThing( data ) {
13001331
if ( thingIsValid( "all", data ) ) {
13011332
dbSave( "thingTable", data );
@@ -1348,7 +1379,7 @@ Another way to write this without `partial` would be:
13481379
}
13491380
}
13501381
}
1351-
1382+
13521383
var saveStuff = saveValidData( validStuff, "stuff" ); // returns a function
13531384
var saveThing = saveValidData( thingIsAllValid, "thing" );
13541385

@@ -1359,7 +1390,7 @@ You could also write "thing" validation like this:
13591390
return thingIsValid( scope, data );
13601391
}
13611392
}
1362-
1393+
13631394
var saveThing = saveValidData( thingValidator( "all" ), "thing" );
13641395

13651396
By making our functions more flexible in how they accept arguments, we make it easier to reuse them. This is functional thinking!
@@ -1369,14 +1400,14 @@ In Clojure we can define a function with multiple argument lists so this becomes
13691400
(defn thing-is-valid
13701401
([scope] (fn [data] (thing-is-valid scope data)))
13711402
([scope data] ... return true or false ...))
1372-
1403+
13731404
(defn save-valid-data
13741405
([validator type] (fn [data] (save-valid-data validator type data)))
13751406
([validator type data]
13761407
(if (validator data)
13771408
(db-save (str type "Table") data)
13781409
(write-log (str "Invalid " type ", not saved")))))
1379-
1410+
13801411
(def save-stuff (save-valid-data valid-stuff "stuff"))
13811412
(def save-thing (save-valid-data (thing-is-valid "all") "thing")
13821413

@@ -1461,7 +1492,7 @@ If FP is so great, why aren't we all using it already?
14611492

14621493
Good question. The OOP industry is vast. Design Patterns, training and consulting, higher education based on
14631494
teaching OOP (ironically after supplanting a lot of courses that taught FP!), testing, tooling, IDEs. The
1464-
momentum behind OOP is huge and the inertia of industry to keep doing things the way they know is almost
1495+
momentum behind OOP is huge and the inertia of industry to keep doing things the way they know is almost
14651496
overwhelming. Yet we see functional features in nearly every new language being designed, we see functional
14661497
features being added to nearly every existing language over time, we see the functional style of programming
14671498
being advocated even in traditional languages -- with less reliance on mutable state. Most of that pressure
@@ -1479,7 +1510,7 @@ Once you've got a taste for Clojure, there are lots of online resources and a ho
14791510
Here's a small sample, roughly in order of approachability:
14801511

14811512
* The online tutorial for learning Clojure, no installation required: http://www.tryclj.com
1482-
* The "4Clojure" puzzles online: https://www.4clojure.com
1513+
* The "4Clojure" puzzles online: https://www.4clojure.com
14831514
* These quickly get hard enough that you'll want a REPL open locally to play with!
14841515
* The Clojure Koans: http://clojurekoans.com
14851516
* You need at least Java, Leiningen, and Git installed for these.

0 commit comments

Comments
 (0)