đź“–ClojureScript Unraveled (2nd edition)
- authors
- Antukh, Andrey and G'omez, Alejandro
- year
- 2019
- url
- https://funcool.github.io/clojurescript-unraveled/
- Short syntax for anonymous functions- #(...)syntax form
- %1,- %2numbered arguments
- number can be omitted if only one argument: %
- variadic form: %&
 
- recur https://funcool.github.io/clojurescript-unraveled/#looping-with-looprecur- recurcan be used inside- loopor functions to call it (recurse) with new bindings- (loop [x 0] (println "Looping with " x) (if (= x 2) (println "Done looping!") (recur (inc x)))) ;; Looping with 0 ;; Looping with 1 ;; Looping with 2 ;; Done looping! ;; => nil (defn recursive-function [x] (println "Looping with" x) (if (= x 2) (println "Done looping!") (recur (inc x)))) (recursive-function 0) ;; Looping with 0 ;; Looping with 1 ;; Looping with 2 ;; Done looping! ;; => nil
 
 
- forsequence comprehension (https://funcool.github.io/clojurescript-unraveled/#for-sequence-comprehensions)- :letnew binding
- :whilefor stopping when condition is false
- :whenfor filtering
- doseqis analogous to- for, but discards result values
 
- identical?predicate tests if two values are actually the same object
- ClojureScript has queues. They use the #queue []syntax.
- Destructuring (https://funcool.github.io/clojurescript-unraveled/#destructuring-section)- :orcan be used to give default values- (let [{name :name :or {name "Anonymous"}} {:language "ClojureScript"}] name) ;; => "Anonymous"
 
- :asto bind original value
- :keysto bind keywords to equivalent names:- (let [{keys [name blah]} {:name "hello" :blah "world"}] name)- keyword syntax works, too (useful for namespaced keywords): (let [{:keys [::name]} {::name "hello"}] name)
 
- keyword syntax works, too (useful for namespaced keywords): 
- :strsand- :symsworks as- :keysbut for strings and symbols.
 
- Threading- ->thread-first (thread as first argument)
- ->>thread-last (thread last argument)
- as->thread-as (allows specifying argument position)- (as-> numbers $ (map inc $) (filter odd? $) (first $) (hash-map :result $ :id 1)) ;; => {:result 3 :id 1}
 
- some->=/=some->>thread-some (short-circuit if function returns- nil)
- cond->=/=cond->>thread-cond (conditionally skip steps)- (defn describe-number [n] (cond-> [] (odd? n) (conj "odd") (even? n) (conj "even") (zero? n) (conj "zero") (pos? n) (conj "positive"))) (describe-number 3) ;; => ["odd" "positive"] (describe-number 4) ;; => ["even" "positive"]
 
 
- Reader conditionals- only work in .cljcfiles
- #?standard- (defn parse-int [v] #?(:clj (Integer/parseInt v) :cljs (js/parseInt v)))
 
- #?@splicing- (defn make-list [] (list #?@(:clj [5 6 7 8] :cljs [1 2 3 4]))) ;; On ClojureScript (make-list) ;; => (1 2 3 4)
 
 
- only work in 
- Polymorphism- Protocols- (defprotocol IInvertible "This is a protocol for data types that are 'invertible'" (invert [this] "Invert the given item."))
- ClojureScript’s protocols are similar to Haskell’s type-classes.
- extend-type- (extend-type TypeA ProtocolA (function-from-protocol-a [this] ;; implementation here ) ProtocolB (function-from-protocol-b-1 [this parameter1] ;; implementation here ) (function-from-protocol-b-2 [this parameter1 parameter2] ;; implementation here ))
 
- extend-protocol- (extend-protocol ProtocolA TypeA (function-from-protocol-a [this] ;; implementation here ) TypeB (function-from-protocol-a [this] ;; implementation here ))
 
- satisfies?can be used to check if value satisfies a protocol:- (satisfies? IFn #{1})
 
- Multi-methods- A more powerful version of protocols
- defmulti+- defmethod
- example- (defmulti say-hello "A polymorphic function that return a greetings message depending on the language key with default lang as =:en=" (fn [param] (:locale param)) :default :en) (defmethod say-hello :en [person] (str "Hello " (:name person "Anonymous"))) (defmethod say-hello :es [person] (str "Hola " (:name person "AnĂłnimo")))
 
 
- Hierarchies- hierarchies allow defining relationship (not only between types)
- deriveto define- (derive ::circle ::shape) (derive ::box ::shape)
 
- ancestors,- descendantsto explore hierarchy- (ancestors ::box) ;; => #[[{:cljs.user/shape} (descendants]] ::shape) ;; => #{:cljs.user/circle :cljs.user/box}
 
- isa?to check- (isa? ::box ::shape) ;; => true (isa? ::rect ::shape) ;; => false
 
- make-hierarchyto define local hierarchihes- (def h (-> (make-hierarchy) (derive :box :shape) (derive :circle :shape)))
 
- :hierarchy #'hcan be used in- defmultito specify hierarchy to use
 
- Types- deftype(defines class)- (deftype User [firstname lastname]) (def person (User. "Triss" "Merigold")) (.-firstname person) ;; => "Triss"
 
- defrecord(slightly higher-level) — records are maps
- reifyallows ad-hoc protocol implementations- (defn user [firstname lastname] (reify IUser (full-name [_] (str firstname " " lastname)))) (def yen (user "Yennefer" "of Vengerberg")) (full-name yen) ;; => "Yennefer of Vengerberg"
 
- specify=/=specify!allow adding protocols to existing JS objects
 
 
- Protocols
- JavaScript interop- js/namespace to access global functions
- (new js/RegExp "^foo$")or- (js/RegExp. "^foo$")(recommended) — calling constructors
- (.test (js/RegExp. "^foo$") "foobar")to call methods
- (js/Math.sqrt 2)— shortcut
- (.-PI js/Math)— property access (shortcut:- js/Math.PI)
- (js-obj "country" "UA")— create JS object
- #js {:country "UA"}— reader macro to create JS object
- set!to set JavaScript object properties
- clj->js=/=js->clj— recursive versions of conversion macros
- into-arrayfor converting arrays
- make-arrayto pre-allocate array length
- aset=/=agetto set/get array value
 
- State management- Atoms- object containing value
- supports watchers
- supports validations
- atomto construct atom
- derefor- @to dereference
- swap!to alter value with a function
- reset!replace value
- add-watch=/=remove-watchfor watchers (identify watcher by specified keyword)
 
- Volatiles- Mutable like atoms, but do not provide watchers and validations, thus are more performant
- volatile!to construct
- deref=/=@
- vswap!,- vreset!
 
 
- Atoms
- Transducers- reducing function is a function that takes accumulator, a value, and returns a new accumulator
- transducer is a function that transforms one reducing function into another reducing function
- eduction can be used to remember a series of transducers (returns an iterator) and will iterate execute them every time, a new element is requested. Applying a composition of transducers would return a seq (and remember values); applying eduction, does not remember sequence.
 
- transient structures allow creating mutable data structures (temporarily)- transientto create
- persistent!to freeze- transient version is invalidated once converted into persistent one
 
 
- Metadata- metato access metadata
- with-metaor- ^{:metavar "val"}syntax to attach metadata
- ^:keywordis a shorthand for- ^{:keyword true}
- ^symbolis a shorthand for- ^{:tag symbol}(e.g.,- ^boolean)
- vary-metato update metadata with function
- alter-meta!or- reset-meta!to update/set metadata in-place
 
- Core protocols- IFn(- -invoke) callable values
- IPrintWithWriter(- -pr-writer) customize printing
- ISeq(- -first,- -rest) implement seq-like
- INext(- -next) for- next
- ISequable(- -seq)
- ICollection(- -conj)
- IEmptyableCollection(- -empty)
- ICounted(- -count)
- IIndexed(- -nth)
- ILookup(- -lookup) for- get
- IAssociative(- -contains-key?,- -assoc)
- IMap(- -dissoc)
- IMapEntry(- -key,- -val)
- IEquiv(- -equiv) for- =
- IComparable(- -compare)
- IMeta(- -meta),- IWithMeta(- -with-meta)
- IEncodeJS(- -clj->js),- IEncodeClojure(- -js->clj)
- IReduce(- -reduce)
- IKVReduce(- -reduce-kv)
- IPending(for- realized?)
- IDeref(for- deref)
- IResetfor- reset!
- ISwapfor- swap!
- IWatchable(- -add-watch,- -remove-watch,- -notify-watches)
- IVolatilefor- vreset!
- IEditableCollectionfor- transient,- ITransientCollectionfor- persistent!
- ITransientAssociativefor- assoc!
- ITransientVectorfor- assoc-n!
- ITransientSetfor- disj!
 
- CSP for Communicating Sequential Processes- built on channels abstraction
- chanto create channel
- put!,- take!,- close!
- put!and- take!take callbacks
- put!returns- trueor- false— whether channel is open
- take!is called with- nilwhen channel closes
- passing nilis not allowed
- currently, there can be only up to 1024 pending puts or takes
- chanhas a buffer argument (either number or a buffer). When place is available in the buffer,- put!succeeds (callback called) immediately
- channels are unbuffered by default
- dropping-bufferlike fixed buffer, but drops extra values (instead of enqueuing them)
- sliding-bufferdiscard oldest values in favor of the new ones
- channels accept transducer as second argument
- chan’s third argument allows handling exceptions
- offer!and- poll!are synchronous versions of- put!and- take!- offer!and- poll!cannot distinguish closed channels
 
- gomacro for running asynchronous “thread.”- >!and- <!for putting and taking from channel- (require '[cljs.core.async :refer [chan <! >!]]) (require-macros '[cljs.core.async.macros :refer [go]]) (enable-console-print!) (def ch (chan)) (go (println [:a] "Gonna take from channel") (println [:a] "Got" (<! ch))) (go (println [:b] "Gonna put on channel") (>! ch 42) (println [:b] "Just put 42")) ;; [:a] Gonna take from channel ;; [:b] Gonna put on channel ;; [:b] Just put 42 ;; [:a] Got 42
 
- timeoutreturns a channel that is going to be closed after the given amount of the milliseconds (use with- take!)
- alts!allow takes and puts to race (it returns whichever was faster)- if :priority trueis passed,alts!will try channels in order
- if :default valueis passed,valueis returned when neither channel is ready
 
- if 
- pipeto pass values from first channel to the second
- pipeline-asynccalls an async function for all values and puts result in the result channel (first argument controls how many functions it can run in parallel)- result values seem to be in the same order as corresponding input values (even if async functions finish in different time)
 
- pipelineruns a transducer for each value in input channel
- splitsplits channel into two (where predicate is true, and where it false)
- reducereduce channel into a single-value channel
- onto-chanpass collection into a channel
- to-chanconverts a collection and returns a channel
- mergemerge multiple channels into one
- multto make channel multicast
- tap,- untap,- untap-allto connect to multicast channel
- pub,- subfor publish-subcsribe
- Mixing channels- mix,- admix,- unmix,- unmix-all
- togglewith- :mute,- :pause,- :solo
- A muted input channel means that, while still taking values from it, they won’t be forwarded to the output channel. Thus, while a channel is muted, all the values put in it will be discarded.
- A paused input channel means that no values will be taken from it. This means that values put in the channel won’t be forwarded to the output channel nor discarded.
- When soloing one or more channels the output channel will only receive the values put in soloed channels. By default, non-soloed channels are muted but we can use solo-modeto decide between muting or pausing non-soloed channels.
 
 
