Today I Learned

hashrocket A Hashrocket project

14 posts about #clojure surprise

Animate ReactNative in ClojureScript with Reagent

Reagent uses ratoms to refresh components. When the state of a ratom is changed, components that deref that state re-render. ReactNative Animation uses a similar technique: changes to an Animated.Value cause a re-render. I was afraid these concepts would collide, and I'd have to bypass Reagent using it's low-level api to use animations. But much to my joyful surprise, Animated.Value changes trigger a re-render in normal Reagent components.

(ns my-app.animation
    ["react-native" :as ReactNative]
    [reagent.core :as r]))

(def animated (.-Animated ReactNative))
(def text (r/adapt-react-class (.-Text ReactNative)))
(def animated-value (.-Value animated))
(def animated-view (r/adapt-react-class (.-View animated)))

(defn testview []
  (let [bounce-value (new animated-value 0)]
    (.setValue bounce-value 1.5)
    (-> bounce-value
        (animated.spring #js {:toValue 0.8 :friction 1})
    (fn [] [animated-view
            {:style {:flex 1
                     :transform [{:scale bounce-value}]
                     :background-color "red"
                     :border-radius 10
                     :shadow-color "#000000"
                     :shadow-opacity 0.7
                     :shadow-radius 2
                     :shadow-offset {:height 1 :width 0}}}
            [text "test"]])))


This may look complex, but compare it to the low level Reagent api used in other examples.

List Functions For A Namespace

You know that clojure.string has a function for uppercasing a string, but you can't quite remember the name of the function. You'd remember if you saw the name though. What you'd like to do is list all the functions in the clojure.string namespace to see if you can pick it out.

You can do just that. There are a couple ways to do it, in fact.

You can use the dir function with Clojure 1.6+. Alternatively, you can grab all the keys from the public intern mappings of the namespace.

> (dir clojure.string)

> (keys (ns-publics 'clojure.string))
(ends-with? capitalize reverse join replace-first starts-with? escape last-index-of re-quote-replacement includes? replace split-lines lower-case trim-newline upper-case split trimr index-of trim triml blank?)


Combinations Of Items From A Sequence

Sometimes we want all combinations of items from a list. For instance, we may have 5 people and we want to know all the ways that we can unique pair up 2 people from that group of 5. What we want is the number of combinations of 2 people from the 5.

The clojure/math.combinatorics library provides a combinations function that gives us exactly that functionality.

(use '[clojure.math.combinatorics :as combo])

(combo/combinations ["Liz", "Tracy", "Kenneth", "Jack", "Jenna"] 2)
; (("Liz" "Tracy") ("Liz" "Kenneth") ("Liz" "Jack")
;  ("Liz" "Jenna") ("Tracy" "Kenneth") ("Tracy" "Jack")
;  ("Tracy" "Jenna") ("Kenneth" "Jack") ("Kenneth" "Jenna")
;  ("Jack" "Jenna"))

The combinations function takes a list of items and then a number for the size of the grouping combinations that it is supposed to produce.

Compose functions

Clojure's comp function allows you to give it a set of functions, and return a function that is the composition of those functions.

;without comp

(defn inc-and-to-string [num]
  (str (inc num)))

(inc-and-to-str 2)
; "3"

;with comp
(def inc-and-to-string-with-comp (comp str inc))

(inc-and-to-string-with-comp 2)

The with-comp version returns a function, which when called, applies inc to the argument passed in, then applies str to the result, the same as the other version.

Load a File in the Leiningen REPL

Leiningen allows us to load a file right in the REPL, with (load-file name):

user=> (load-file "guess_the_number_with_feedback.clj")

This returns the name of the last function defined, which is useful, because that's likely to be our main function or equivalent (which we would then call).

Here is the documentation, also from the REPL:

user=> (doc load-file)
  Sequentially read and evaluate the set of forms contained in the file.

Aggregation Using merge-with

Clojure provides the merge-with function as a way of conjoining a series of maps. You must provide merge-with a function that it can use to merge two values for matching keys. For instance, imagine you have a bunch of maps that contain counts for entities identified by keywords. You can consolidate the sum of all the counts into a single map using the merge-with function combined with the + function.

> (merge-with + {:a 1 :b 3} {:b 2 :c 3} {:c 1 :d 4})
{:a 1, :b 5, :c 4, :d 4}

For different kinds of data, a different function argument may be more appropriate. For instance, aggregating lists instead of integers calls for the concat function:

> (merge-with concat {:a '(1 2) :b '(3 4)} {:c '(3 4) :a '(5 4 1)})
{:a (1 2 5 4 1), :b (3 4), :c (3 4)}

Try A Clojure Project In The REPL

The lein-try plugin is a tool that makes it easy to quickly try out a Clojure project in the Lein REPL. Given the name and version of a Clojure project, it will drop you into a REPL with that project loaded in. This is a great way to get the feel for the features of an unfamiliar Clojure library before dropping it in as a dependency to your own project.

First, add the plugin to your ~/.lein/profiles.clj file by including the following line in the :plugins vector:

[lein-try "0.4.3"]

Then simply invoke the plugin with whatever project you want to try:

$ lein try automat

And to include a specific version number:

$ lein try automat "0.1.3"

Pretty Print The Last Thing

Clojure provides pp as a convenience macro for pretty printing the last thing that was output. If you are playing around with a function in the repl, trying to get the output just right, pp can come in handy.

> (fancy-func)
{:one {:a 1, :b 2, :c 3, :d 4}, :two {:b 2, :c 3, :d 4, :e 5}, :three {:c 3,
:d 4, :e 5, :f 6}, :four {:d 4, :e 5, :f 6, :g 7}}
> (clojure.pprint/pp)
{:one {:a 1, :b 2, :c 3, :d 4},
 :two {:b 2, :c 3, :d 4, :e 5},
 :three {:c 3, :d 4, :e 5, :f 6},
 :four {:d 4, :e 5, :f 6, :g 7}}

See (doc pp) for more details.