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