Fullstack FP in Clj/Cljs
with
Datomic, Ring, Om, & Garden
Priyatam Mudivarti
Principal, Facjure LLC
LambdaConf
Boulder, CO 2015
Why?
Templates
Database Modeling
Http Apis
Responsive Design
UI/Events
Services
manager
All HTTP Requests must go through api defaults used by the industry. Use decent static site defaults, too.
λgrammer
Let me write a higher order function
db analyst
What is the history of changes on this article id as-of Dec 25th?
λgrammer
?
designer
Make the headings serifs and scale-types using four breakpoints (480 px increments). Use our brand settings on a 3-column grid
λgrammer
Let me write a higher order function
Title Text
FP in Clojure/Clojurescript
My version, anyway.
Data
Composition
Abstractions
Let's start with Data
life without types
It is better to have 100 functions operate on one data structure than to have 10 functions operate on 10 data structures
- Alan J. Perlis
[
{:poem/title "I am not a hacker"}
{:poem/book :poets-and-hackers}
{:poem/year 2015}
{:author/name "Priyatam Mudivarti"}
{:author/email "priyatam@facjure.com"}
{:author/gender :male}
{:tag/names #{:hackers :poetry-slam :boulder-cafe :fp-rocks}
{[1 2] :one-two }}
]
Edn, a data transfer format
[;; A user writing an article
{:db/id #db/id [:db.part/user -100]
:user/username "john.smith"}
{:db/id #db/id [:db.part/user -200]
:category/name "Functional Programming"}
{:db/id #db/id [:db.part/user -300]
:article/title "Monads in Pictures"
:article/author #db/id [:db.part/user -100]
:article/category #db/id [:db.part/user -200]
:article/body "http://bit.ly/14mj7WG"}
]
Datomic facts (data) and queries are Edn
;; Requests and Response are immutable Maps
;; Handlers return Response
(defn what-is-my-ip [request]
{:status 200
:headers {"Content-Type" "text/plain"}
:body (:remote-addr request)})
;; Middlewares are higher-order functions that transform a handler
(defn wrap-content-type [handler content-type]
(fn [request]
(let [response (handler request)]
(assoc-in response [:headers "Content-Type"] content-type))))
;; Application
(def app
(-> what-is-my-ip
(wrap-content-type "text/html")
(wrap-keyword-params)
(wrap-params)))
HTTP interaction as Data
(html
[:li {:class (if active "active" "")}
[:a {:href (str "#" path)} name]])
HTML as Data
(defn view [data owner]
(reify
om/IRenderState
(render-state [_ {:keys [sample-text]}]
(let [text (:sample-text data)]
(html
[:section {:class "home"}
[:h1 text]
[:h2 text]
[:h3 text]
[:h4 text]
[:h5 text]
[:h6 text]
[:p.large text]
[:p.medium text]
[:p.small text]])))))
Virtual Dom as Data
(defn clearfix [clazz]
[clazz {:*zoom 1}
[:&:before :&:after {:display "table"
:content " "
:line-height 0}]
[:&:after {:clear "both"}]])
CSS as Data
What if?
A data structure is just a stupid programming language
— R. Wm. Gosper
Datastructures in Clj/Cljs
immutable, lazy sequences
functions that create sequences
functions that filter sequences
sequence predicates
functions that transform sequences
;; list comprehension
(for [x (range 1 10)
y (range 1 10)
:when (and (zero? (rem x y))
(even? (quot x y)))]
[x y])
;; higher order functions over sequences
(map inc [0 1 2 3 4 5 6 7 8 9])
(filter even? (range 10))
(reduce + (range 100))
(partition 2 [:a 1 :b 2 :c 3 :d 4 :e 5])
(remove odd? (range 10))
(take 5 (interleave (repeat "red") (repeat "blue")))
Apply built-in functions, try to be pure
a lot more
transducers, pmap, comp ...
(map (comp - (partial + 3) (partial * 2)) [1 2 3 4]) ;;=> (-5 -7 -9 -11)
Quick, convert this
/type/author /authors/OL999930A 2 2008-08-20T17:56:50.993393 {"name": "Onur Akdog\u0306u", "personal_name": "Onur Akdog\u0306u", "last_modified": {"type": "/type/datetime", "value": "2008-08-20T17:56:50.993393"}, "key": "/authors/OL999930A", "type": {"key": "/type/author"}, "revision": 2}
*15 million books in open-data format
(defn parse-csv [str-in]
(csv/read-csv str-in :separator \tab))
(defn read-csv [data-in]
(with-open [f (io/reader (io/resource data-in))]
(doall (parse-csv f))))
(defn parse-json [data-in]
(->> data-in
(map #(nth % 4)) ;; 5th item is json data
(apply str)
(json/parse-string)
walk/keywordize-keys))
(defn to-data [res-in]
(with-open [res (io/reader (io/resource res-in))]
(->> res
(parse-csv)
(parse-json)
(timeify))))
to this
Demo
datomic, ring, http
Backend workflow
ring/compojure
http-kit / nginx
datomic/postgres*
dumb pipes,
smart transformations,
datalog queries
*even clj sql libraries treat adbc resultsets as Data
Datomic Concepts
Datomic is an Information system.
Create Facts: Identity, Entities, Attributes, Values, Transactions
Find Facts: Query, DbFunctions, and Rules
Manage Facts: Peers, Transactors, Storage Engines
Datomic Architecture
source: http://docs.datomic.com/architecture.html
Why I like Datomic
Everything is a Fact: a 4-tuple datom created through a transaction, with implicit time
[entity attribute value transaction]
Queries and Rules are logic-constraints (expressed in where data clauses)
An append-only database—values don't change, time travel is free!
Database-functions can perform transactor-time operations
Why I like Ring
It's just function or higher-order function.
Middlewares decompose your problem into small reusable units
Adapts to most http-servers (sync/async) from a single abstraction
Best practices can be curated
UI workflow
content, styles, and async http responses as data
om/sablono
secretary
garden/mesh
core.async
Om
ecosystem
global app-state with lenses-like cursors for safe data-access
use props for only transient state
plug routers, js libs
follow react life-cycle protocols
async event handling with core.async
Let's talk about CSS/SASS
no namespaces
can't compose HTML and CSS in the same syntax
css doesn’t “talk to dom”
math impl is hard
can we pass css as functions at runtime?
mixins are not composable
constrained by limitations—not by choice
programming stylesheets
in FP
separate selectors and declarations
Ring-style middleware
Mesh, a grid & typography library
http://github.com/facjure/mesh
(defn create-minimal-grid [clazz pad]
[[:* {:box-sizing "border-box"}]
[clazz {:background "white"
:margin [[0 0 pad 0]]}
[:&:after {:content ""
:display "table"
:clear "both"}]
["[class*='col-']" {:float "left"
:padding-right pad }]
[:.col-1-3 {:width "33.33%"}]
[:.col-2-3 {:width "66.66%"}]
[:.col-1-2 {:width "50.00%"}]
[:.col-1-4 {:width "25.00%"}]
[:.col-1-8 {:width "12.50%"}]
[:.out-padding {:padding [[pad 0 pad pad pad]]}
["[class*='col-']:last-of-type"
{:padding-right pad}]]]])
A grid in a single function
(defn modular-scale-fn [base ratio]
(let [[up down] (if (ratio? ratio)
(if (< (denominator ratio)
(numerator ratio))
[* /]
[/ *])
(if (< 1 ratio) [* /]
[/ *]))
f (float ratio)
us (iterate #(up % f) base)
ds (iterate #(down % f) base)]
(memoize
(fn ms [n]
(cond
(< 0 n) (if (whole-number? n)
(nth us n)
(let [m (Math/floor (float n))
[a b] [(ms m) (ms (inc m))]]
(+ a (* (Math/abs (- a b))
(- n m)))))
(< n 0) (if (whole-number? n)
(nth ds (Math/abs n))
(let [m (Math/floor (float n))
[a b] [(ms m) (ms (dec m))]]
(+ a (* (Math/abs (- a b))
(- n m)))))
:else base)))))
modular
scale, in a single function
Layouts
https://github.com/facjure/mesh
(def gutter (px 20))
(def typesetting
(list
(typo/typeset-body typo/defaults :golden)))
(def grids
(list (typo/baseline (rgba 0 0 255 0.5) (px 2))
(grid/create ".grid" gutter)
(grid/wrap-widths 978)
(grid/create-nested-units)
(grid/nuke-gutters-and-padding)
(grid/respond-small (:mobile breakpoints) gutter)
(grid/respond-medium (:tablet breakpoints))))
(def styles
(css (merge grids typesetting)))
Abstraction is at the center of most work in
Computer Science &
User Interface Design.
Abstractions
For ex, decouple
selectors * declarations
;; a bad example
(defn make-serifs [selector families]
(fn [declarations]
(let [styles (selector declarations)]
(conj styles (font (:garamond families) 3 600 0.5 2)))))
;; slightly better
(defn scale-type [selector params]
(fn [declarations]
(let [styles (selector declarations)]
(conj styles
(at-media {:min-width (get-in params [:breakpoints :mobile])}
[:& {:font-size (* 1.5 (:min-font params))}])
(at-media {:min-width (get-in params [:breakpoints :tablet])}
[:& {:font-size (* 1.75 (:min-font params))}])
(at-media {:min-width (get-in params [:breakpoints :laptop])}
[:& {:font-size (* 2.25 (:min-font params))}])))))
Compose Declarations
and Selectors, with HoF
(defn headings [declarations]
[:h1 :h2])
(-> headings
(scale-type settings)
(make-serifs brand-fonts)
(make-sans-brand-fonts)
(grid/create {:columns 3})
Create your own DSLs
Demo
om, secretary, garden, mesh
So
everything is Data
a sequence of values
pipe, compose
UNIX was not designed to stop its users from doing stupid things, as that would also stop them from doing clever things.
- Doug Gwyn
References
Demos created with https://github.com/priyatam/mala https://github.com/priyatam/ring-micro
https://github.com/facjure/mesh https://github.com/facjure/atomic Libraries
https://github.com/ring-clojure
https://github.com/omcljs/om
https://github.com/noprompt/garden
https://github.com/noprompt/secretary
https://github.com/bhauman/lein-figwheel
https://slides.com/priyatam/fullstack-clj-cljs
@priyatam
priyatam@facjure.com
http://github.com/priyatam
Fullstack FP in Clj/Cljs
By Priyatam Mudivarti
Fullstack FP in Clj/Cljs
What happens when we design a product entirely using FP?What are our abstractions? From choosing a functional database to building http apis and designing templates and stylesheets—it is possible these days, to stay entirely in FP land!
- 1,071