Skip to content

Latest commit

 

History

History
529 lines (432 loc) · 12.7 KB

day-2.md

File metadata and controls

529 lines (432 loc) · 12.7 KB

Day 2

2.a Standard - Indentation

Body indentation

;; good
(when something
  (something-else))

(with-out-str
  (println "Hello, ")
  (println "world!"))

;; bad - four spaces
(when something
    (something-else))

;; bad - one space
(with-out-str
 (println "Hello, ")
 (println "world!"))

Function Argument alignment

;; good
(filter even?
        (range 1 10))

;; bad - argument aligned with function name (one space indent)
(filter even?
        (range 1 10))

;; bad - two space indent
(filter even?
        (range 1 10))

Function Argument Indentation

;; good
(filter
  even?
  (range 1 10))

;; bad - two-space indent
(filter
  even?
  (range 1 10))

Binding Alignment

;; good
(let [thing1 "some stuff"
      thing2 "other stuff"]
     (foo thing1 thing2))

;; bad
(let [thing1 "some stuff"
      thing2 "other stuff"]
     (foo thing1 thing2))

Gather Trailing Parentheses

;; good; single line
(when something
      (something-else))

;; bad; distinct lines
(when something
      (something-else)
      )

Empty lines between Top-Level Forms

;; good
(def x ...)

(defn foo ...)

;; bad
(def x ...)
(defn foo ...)

;; Exception for grouping of related def’s together

;; good
(def min-rows 10)
(def max-rows 20)
(def min-cols 15)
(def max-cols 30)

No blank lines within definition forms

;; okay - the line break delimits a cond pair
(defn fibo-iter
      ([n] (fibo-iter 0 1 n))
      ([curr nxt n]
       (cond
         (zero? n)
         curr

         :else
         (recur nxt (+ curr nxt) (dec n)))))

;; bad
(defn fibo-iter
      ([n] (fibo-iter 0 1 n))

      ([curr nxt n]
       (cond
         (zero? n) curr

         :else (recur nxt (+ curr nxt) (dec n)))))

2.b Standard - Namespace

Avoid single-segment namespaces (conflict risks)

;; good
(ns example.ns)

;; bad
(ns example)

2.c Standard - Naming

Use lisp-case for function and variable names

;; good
(def some-var ...)
(defn some-fun ...)

;; bad
(def someVar ...)
(defn somefun ...)
(def some_fun ...)

Use CapitalCase for protocols, records, structs and types

;; good
(defprotocol Moveable ...)
(defrecord Car ...)

;; bad
(defprotocol moveable ...)
(defrecord car ...)

The names of predicate functions (returning a boolean) should end in a question mark

;; good
(defn palindrome? ...)

;; bad
(defn palindrome-p ...) ; Common Lisp style
(defn is-palindrome ...) ; Java style

Use earmuffs for things intended for rebinding

;; good
(def ^:dynamic *a* 10)

;; bad
(def ^:dynamic a 10)

Don’t use a special notation for constants; everything is assumed a constant

;; good
(def max-size 10)

;; bad
(def MAX-SIZE 10) ; Java style
(def +max-size+ 10) ; Common Lisp style, global constant
(def *max-size* 10) ; Common Lisp style, global variable

Use ‘_’ for destructuing targets and formal argument names whose value will be ignored by the body form

;; good
(let [[a b _ c] [1 2 3 4]]
  (println a b c))

(dotimes [_ 3]
  (println "Hello!"))

;; bad
(let [[a b c d] [1 2 3 4]]
  (println a b d))

(dotimes [i 3]
  (println "Hello!"))

For documenting-purpose, you can explicit a name for unused arguments or maps by prepending a ‘_’

;; good
(defn myfun1 [context _]
 (assoc context :foo "bar"))

(defn myfun2 [context {:keys [id]}]
 (assoc context :user-id id))

;; better
(defn myfun1 [context _user]
 (assoc context :foo "bar"))

(defn myfun2 [context {:keys [id] :as _user}]
 (assoc context :user-id id))

2.d Standard - Function

Separate function name and argument from function body

;; good
(defn foo  "My foo documentation"
  [x]
  (bar x))

;; good
(defn foo [x]
  (bar x))

;; bad
(defn foo
  [x] (bar x))

Indent each arity form of a function definition vertically aligned with its parameters

;; good
(defn foo
  "I have two arities."
  ([x]
   (foo x 1))
  ([x y]
   (+ x y)))

;; bad - extra indentation
(defn foo
  "I have two arities."
  ([x]
    (foo x 1))
  ([x y]
    (+ x y)))

Sort arity of a function from fewest to most arguments

;; good - it's easy to scan for the nth arity
(defn foo
  "I have two arities."
  ([x]
   (foo x 1))
  ([x y]
   (+ x y)))

;; bad 
(defn foo
  ([x] 1)
  ([x y z] (foo x (foo y z)))
  ([x y] (+ x y))
  ([w x y z & more] (reduce foo (foo w (foo x (foo y z))) more)))

Don’t define ns vars inside functions

;; very bad
(defn foo []
  (def x 5)
  #_body)

Don’t shadow clojure.core names with local bindings

;; bad - clojure.core/map must be fully qualified inside the function
(defn foo [map]
  #_body)

2.e Standard - Threading

Don’t shadow clojure.core names with local bindings

;; good
(-> [1 2 3]
    reverse
    (conj 4)
    prn)

;; not as good
(prn (conj (reverse [1 2 3])
           4))

3. Eco-system - Clojure eco-system

4.a Url Shortener

Feature: Url shortened creation
  Scenario: A user creates his shortened url
    Given a user "dpanza"
    When "dpanza" creates a short url for "https://clojure.org/"
    Then the short url is stored
    And a short url is given to user

Feature: Manage url shortened
  Scenario: A user lists all the urls created
    Given a user "dpanza"
    And existing shortened urls
      | user   | link                 | shortened | visits |
      | dpanza | https://clojure.org/ | clj       | 12     |
    When "dpanza" gets his urls shortened    
    Then the returned list of url shortened return "1" results
    Then the list contains link for "https://clojure.org/" shortened to "clj" visited by "12" users

Feature: Redirect visitors
  Scenario: A visitor clicks a shortened url
    Given existing shortened urls
      | user   | link                 | shortened | visits |
      | dpanza | https://clojure.org/ | clj       | 12     |
    When a visitor clicks on a shortened url "clj"
    Then the visitor is redirected to "https://clojure.org/"
    And the number of visits for the shortened url "clj" of "dpanza" is incremented

5.a Backend - Initiate a clojure app project

# Create a project using tools-deps
clojure -Ttools install com.github.seancorfield/clj-new '{:git/tag "v1.2.404"}' :as clj-new

clojure -Tclj-new app :name defsquare/training

ls –l 
 CHANGELOG.md
 LICENSE
 README.md
 build.clj
 deps.edn
 doc
 pom.xml
 resources
 src
 test

5.c Backend - State management

https://github.com/tolitius/mount

in your deps.edn file, add mount

{:deps {mount/mount {:mvn/version "0.1.17"}}}

in a clojure namespace

(defstate my-state          
          :start (do (prn "starting") {:status :run})
          :stop (do (prn "stopping") {:status :stopped}))

(defn -main [& args] (mount.core/start))

(comment
  (main) 
  (mount.core/start)
  (mount.core/stop))

6.a API - Web server

https://github.com/ring-clojure/ring

in your deps.edn file, add mount

{:deps {ring/ring-core          {:mvn/version "1.10.0"}
        ring/ring-jetty-adapter {:mvn/version "1.10.0"}}}

;; in a clojure namespace

(defn handler [_request]
      {:status  200
       :headers {"Content-Type" "text/html"}
       :body    "Hello World"})

(defstate server          
          :start (run-jetty handler {:port 8080 :join? false})
          :stop (.stop server))

6.b API - Manual routing

(defmulti router (fn [{:keys [uri request-method] :as _request}]
                     [request-method uri]))

(defmethod router [:get "/api/entity"]
           [_request]
           {:status  200
            :headers {"Content-Type" "text/html"}
            :body    "This is an entity!"})

(defmethod router :default
           [{:keys [uri]}]
           {:status  404
            :headers {"Content-Type" "text/html"}
            :body    (str "Request '" uri "' not found")})

(defstate server
          :start (run-jetty router {:port  8080 :join? false})
          :stop (.stop server))

6.c API - Router

https://github.com/metosin/reitit

in your deps.edn file

{:deps {metosin/reitit {:mvn/version "0.7.0-alpha5"}}}

in a clojure namespace

(def app
  (ring/ring-handler
    (ring/router      ["/api"
                       ["/entity" {:get (fn [_request]
                                            {:status  200
                                             :headers {"Content-Type" "text/html"}
                                             :body    "This is an entity!"})}]])))

(comment  
  (app {:request-method :get, :uri "/api/entity"}))

6.d API - Encode/Decode

(:require 
  [muuntaja.core :as m]
  [reitit.ring.coercion :as coercion]
  [reitit.ring.middleware.muuntaja :as muuntaja]
  [reitit.ring.middleware.parameters :as parameters])

(def app
  (ring/ring-handler
    (ring/router
      ["/api"
       ["/entity/:id"
        {:post         
         {:handler
          (fn [request]
              (let [{:keys [body-params path-params query-params]} request]
                   {:status 200 :body   ""}))}}]]
      {:data {:muuntaja   m/instance
              :middleware [muuntaja/format-middleware
                           coercion/coerce-exceptions-middleware
                           coercion/coerce-request-middleware
                           coercion/coerce-response-middleware
                           parameters/parameters-middleware]}})))

7.a Persistence - Inmem repository

(def initial-state {:users {}})
(def db (atom initial-state))

@db
;; => {:users {}} 

(swap! db assoc-in [:users :dpanza] {:foo "bar"})
;; => {:users {:dpanza {:foo "bar"}}}

@db
;; => {:users {:dpanza {:foo "bar"}}}

(reset! db initial-state)
;; => {:users {}} 

@db
;; => {:users {}}

Previous Day | Go to home | Next Day