Fork me on GitHub
#clojure
<
2017-12-11
>
qqq05:12:23

I'm sure I've asked this before. Is https://clojuredocs.org/clojure.core/count constant time? if not, what is ?

noisesmith05:12:54

count is constant time if the object you call it on is counted?

qqq05:12:07

I'm counting a vector.

noisesmith05:12:20

yes that's constant time

noisesmith05:12:38

+user=> (counted? [])
true

qqq05:12:46

to be pedantic, that proves that the empty vec is counted, it says nothing about vecs in general šŸ™‚

noisesmith05:12:09

there are no collections that special case- counted? is checking for implementation of an interface

qqq06:12:57

does clojure have an insert-at-index ?

qqq06:12:07

I want '(1 2 3) ':a -> '(1 :a 2 3)

qqq06:12:15

where I want to insert into thedlist at the ith-index

noisesmith06:12:12

there's a finger tree library that works that way

dominicm09:12:50

I couldn't see how to insert at index with finger tree. Am I missing something obvious?

dominicm09:12:27

Oh, derp, I just noticed the assoc

qqq06:12:37

yeah, I guess with either vector or list, insertion at arbitrary index can be expensive

Empperi07:12:13

Hmm, I kinda wonder what is the reasoning to add the CLI tools into Clojure 1.9? I kinda get the ā€œcljā€ command to easily launch a Clojure REPL without first having to create a leiningen/boot project but rest of the stuff is kinda not-so-necessary to me?

Empperi07:12:56

Not saying it isnā€™t ok that it is there now but I would have prioritised things differently for 1.9 release šŸ‘

Empperi07:12:20

itā€™s like a poor manā€™s Leiningen or Boot

Empperi07:12:39

maybe itā€™s there to lower the barrier of entry for newbies?

qqq08:12:25

is there a more idiomatic way to write: (vec(apply concat (for ...)))

qqq08:12:35

(vec (apply concat (for ...)))

fmind08:12:38

@niklas.collin I think it makes sense if you consider other ecosystems. We need at least 3 tools to work with a language: an interpreter/compiler, a dependency manager and a build tool

fmind08:12:54

before, we had boot and lein to implement all these tools. But now, we can have a clean separation with CLI tools providing the interpreter and dependency manager, and lein/boot providing the build tool

fmind08:12:39

I also like that tools.deps.alpha attempts to standardize the dependencies in Clojure project

fmind08:12:41

and I remember how confuse I was when I tried to learn Clojure. I didn't understand why there was no "clojure interpreter", and we had to rely on leiningen

fmind08:12:17

it would be like for C++, we had to install cmake to use the language

fmind08:12:11

@qqq you could use mapcat instead of the for loop (vec (mapcat f coll))

Empperi09:12:48

was just about to say the same thing to qqq šŸ™‚

mbjarland09:12:40

is there an idiomatic way of replacing every :f in [\a \b :f \c \d :f \e \f :f] with [1 2 3] to give [\a \b 1 \c \d 2 \e \f 3], i.e. find occurence of a specific element (keyword) in a vector an replace with element at index in another vector. I have a somewhat convoluted reduce for this right now but I get the feeling there might be something better

gklijs09:12:22

Donā€™t really see anything else then reduce being simpler, you can start the reduce on [0[]] and either conj the value to the second part, or add and to the first value and add both the new first value and conj it?

nblumoe09:12:04

@niklas.collin have you seen https://www.youtube.com/watch?v=sStlTye-Kjk already? This might give some additional insights about the introduced CLI tooling.

mbjarland09:12:28

@gklijs more or less what I'm doing. Thanks for the sanity check.

Empperi09:12:23

@nblumoe nope, will check

nblumoe09:12:47

and then there is also the documentation, e.g. https://clojure.org/reference/deps_and_cli

nblumoe09:12:57

here would be an entry point into the talk specifically for clj: https://youtu.be/sStlTye-Kjk?t=18m19s but for context it might be best to watch from the beginning

Empperi09:12:27

@nblumoe the core thing I got out of that thing is that Clojure 1.9 is actually 3 jars these days, so based on that it does make sense to build dependency management into Clojure itself to avoid the hassle that would create for users

nblumoe09:12:27

@niklas.collin yeah and as @alexmiller also said in the talk it (for now) serves as a hassle-free entry point for newcomers as well as a quick way to ā€œjust start a REPLā€. But I think it fits into the larger picture about dependency management quite well, untangling that from other tasks that for example boot and lein take care of and also I suspect that this might be a step towards solving issues that were outlined in https://www.youtube.com/watch?v=oyLBGkS5ICk Maybe @alexmiller wants to elaborate a bit on the future of clj? šŸ™‚

nblumoe09:12:43

aaaaand I am already using it for real to programmatically get a Clojure environment to eval code in šŸ™‚

New To Clojure10:12:21

@nblumoe What could be easier than this?

brew install leiningen
lein repl
When I started learning Clojure I was surprised to discover how easy it is to get working REPL and to install clojure-mode and inf-clojure (that's what Rich Hickey uses).

qqq10:12:29

http://www.eff-lang.org/handlers-tutorial.pdf <-- has anything like this been implemented in clojure ?

tbaldridge15:12:23

@U053S2W0V has been working on a lot of that sort of stuff for some time. IIRC he got some of it working with stock clojure

tbaldridge15:12:07

But sadly, to implement multi-shot handlers in Clojure you'd need some sort of delimited continuation support. The JVM makes that close to impossible (as does almost every other VM on the planet)

bbloom17:12:40

Yeah, at @tbaldridge says, this is definitely something Iā€™ve explored a bunch. Unfortunately, the various multi-shot tricks are never going to work, but aborting (zero-shot?) is possible via exceptions, and the standard one-shot is pretty easily replicated by simple mutable state. Better yet, 1-shot can be modeled with dynamic variables pretty directly.

bbloom17:12:09

just like you can bind out, you can just bind a ^:dynamic function if you want

bbloom17:12:34

and you can play games with bound-fn, with-bindings, and macros and such to create reusable handlers

qqq19:12:56

@U053S2W0V @tbaldridge: is there a nice talk of this somewhere? regardless of whether it can be implemented in clojure, I want to understand how it works

qqq20:12:12

@U053S2W0V: would you recommend installing https://github.com/matijapretnar/eff and playing with it for a weekend ?

bbloom20:12:36

ĀÆ\(惄)/ĀÆ if you want

bbloom20:12:00

if you already know ml/ocaml, itā€™s reasonable to play with it, but last i tried it, it was obviously academic demoware

qqq20:12:14

not for production use, the full question is: suppose I wanted to spend a weekend learning how Effects work would the most efficient route be to download eff and work through a few tutorials in it?

qqq20:12:20

@U053S2W0V: ^^ // also, is this the same "eff" system that purescrit uses ?

bbloom20:12:21

i canā€™t really answer that question, since i already had a grasp on the fundamental topics via the various analogies i describe in my talk - youā€™ll have to figure out a learning path that works for you

bbloom20:12:57

Iā€™m not super familiar with PureScript, but I believe it is a haskell-like and therefore Eff in Purescript is the ā€œEff Monadā€ which is more or less the same concept, yes

bbloom20:12:37

thereā€™s also Oleg Kiselyovā€™s writings on ā€œExtensible Effectsā€, ā€œExtensible Interpretersā€, ā€œFreer Monadsā€, etc

qqq10:12:40

why do we have empty? and not-empty but no not-empty?

mrchance10:12:55

Hi, does anyone have a good idea how to turn a stream of events into a lazy sequence of tails? e.g. (tails odd? [1 2 3 4 5]) ; => ((1 2 3 4 5) (3 4 5) (5))

mrchance10:12:48

Maybe something like this?

(defn partition-tails [pred col]
  (let [[matching & rst :as tail] (drop-while (complement pred) col)]
    (if matching
      (lazy-seq (cons tail (tails rst)))
      ())))

mrchance11:12:46

Don't have much experience with explicit lazy sequences...

dominicm10:12:13

@qqq not-empty? is seq iirc.

qqq10:12:25

(defn lwhile [cur test update]
  (loop [cur cur]
    (if (test cur)
      (update cur)
      cur)))

mrchance11:12:48

Don't you need a recur in there somewhere? :thinking_face:

mrchance11:12:50

And if it is where I suspect, then I don't think there is a builtin... It's something I have been missing quite a few times as well

qqq11:12:33

sorry, it's (recur (update cur))

tbaldridge15:12:11

you can do that with reduce I think

tbaldridge15:12:35

but a loop may be easier.

tbaldridge15:12:03

And actually I don't think you need the loop in there ethier

tbaldridge15:12:45

(defn fix-point [cur test update]
  (if (test cur)
    (recur (uprate cur) test update)
    cur))

noisesmith18:12:25

you can do this with drop-while and iterate

=> (first (drop-while #(< % 100) (iterate #(* 2 %) 3)))
192

qqq10:12:30

is there a builtin for the lwhile defined above ?

mbjarland11:12:46

granted, somewhat unorthodox use of core async...

mbjarland11:12:41

oh and my solution with partition-by and interleave above was crap

gklijs11:12:34

(def test-vec [\a \b \c :f \d:f \e \f :f]) 
(second (reduce #(if (= :f %2)(let [n (+ 1 (first %1))][n (conj (second %1) n)])[(first %1)(conj (second %1) %2)]) [0 []] test-vec))
[\a \b \c 1 \d 2 \e \f 3]

mbjarland11:12:29

@gklijs doesn't that just increment the 1, 2, 3? the thought was that those values would come from a second vector and could be anything, i.e. the second vector could be [:x :y :z] and give [\a \b \c :x \d :y \e \f :z]?

gklijs11:12:43

yes, sou your looking for something else

mbjarland11:12:26

guess I'm starting to like channels for this, feel free to rid me of my infatuation with my newfound use of channels if this is for some reason a bad idea

mbjarland11:12:03

guess the general question would be if it is bad style to pull in core.async just to manage state even in the absence of any concurrency aspects

rauh11:12:36

@mbjarland You never do anything in your first benchmark. map is lazy.

mbjarland11:12:33

@rauh - doh, thank you. Not sure how many times I've done this now

rauh11:12:48

Also: You have to recreate your channel among interations. The second loop you'll just insert nil all the time.

rauh11:12:52

If you want something stateful and avoid core.async hack you can just create a function like that:

(defn make-iter
  [x]
  (let [idx (atom -1)]
    (fn []
      (nth x (swap! idx inc)))))

gklijs11:12:57

(second (reduce #(if (= :f %2)[(rest (first %1)) (conj (second %1) (first (first %1)))][(first %1)(conj (second %1) %2)]) [[1 2 3] []] test-vec))

mbjarland12:12:53

Ok, Monday, making too many mistakes, signing off

borkdude15:12:05

Does it make sense for clojure to support this kind of type hinting?

(defn f [^longs [x y z]]
    (+ x y z))

borkdude15:12:21

so for individual elements it will be inferred that they are longs?

bronsa15:12:36

^longs means long array

borkdude15:12:17

@bronsa > What about when you have a sequence of values, all of a uniform type? Clojure provides a number of special hints for these cases, namely ^ints, ^floats, ^longs, and ^doubles. https://github.com/clojure-cookbook/clojure-cookbook/blob/master/08_deployment-and-distribution/8-05_type-hinting.asciidoc

borkdude15:12:54

also (apply + ^longs (list 1 2 3)) works, maybe just by luck

mfikes15:12:37

That lets you do this without reflection for the Math/abs call FWIW:

(defn f [^longs arr] (Math/abs (aget arr 0)))
(f (into-array Long/TYPE [-1]))

bronsa15:12:37

@borkdude the line after the one you posted: >Hinting these types will allow you to pass whole arrays as arguments to Java functions and not provoke reflection for sequences

bronsa15:12:55

the fact that your apply expression works is because the type hint is just never used there

bronsa15:12:25

no, don't use type hints on vectors

bronsa15:12:28

it just doesn't do anything

borkdude15:12:36

good point, thanks ā€” text is a bit ambiguous about it

bronsa15:12:40

at worse it can cause the compiler to crash

danm15:12:20

Is there any handy function in Java or Clojure to coerce numbers and strings to long? We're parsing json, and there are fields we know are numerical, but sometimes they're strings and sometimes they're raw numbers. The json lib (Cheshire) converts the raw numbers to Integer, not Long, so we then can't use (Long. ...) on that, but equally (long ...) doesn't work when the value is a String rather than a number

borkdude15:12:32

One minor question: 1:

(defn f [[x y z]]
  (apply + [x y z]))
2:
(defn f [[x y z]]
  (+ x y z))
why does Clojure give an uncheked warning on the last one, but not the first?

rauh15:12:40

Long/valueOf? @carr0t

danm15:12:52

There's still no (Long/valueOf ...) that takes an Integer, sadly

borkdude15:12:27

@carr0t #(Long/parseLong (str %)) ?

danm15:12:08

That would work, yes. It's just a bit horrible šŸ˜‰ But it would work...

borkdude15:12:58

@carr0t or something with number? or string? would work

bronsa15:12:14

(cond-> x (string? x) (Long/valueOf))

danm15:12:34

Just before I wrote something to wrap Long. for string and long for number I was wondering if there was something that already did that for me

danielstockton15:12:38

kinsky or franzy for working with kafka?

danm15:12:56

TBH we just have a very thin shim over the Java native stuff

danm15:12:03

For Kafka I mean

danielstockton15:12:56

@carr0t That's an approach I often like to take too, with other libs

Prakash15:12:21

@carr0t if you know the keys for which u want this (str or int ->long ) conversion, I think cheshire supports supplying custom decode functions to specify types

danm15:12:54

Will have to see if they think it's interesting enough to let me talk šŸ˜‰

New To Clojure16:12:56

Is that true that using functional programming usually has memory usage overhead? For example, consider a typical task and typical FP solution for it (it was suggested by consultant). Task: count all values equal to 10 in an array. FP solution: (count (filter #(= % 10) values-array)) which includes creation of intermediate array with all 10's. What do you think?

borkdude16:12:45

Generally true for operations on sequences creating more sequences, because it all has to be garbage collected, but if that becomes a problem you can either write a more efficient version using reduce, or use transducers, or use transients

borkdude16:12:47

E.g. using @cgrand's xforms lib: (x/count (filter #(= % 10)) seq)

New To Clojure16:12:51

@borkdude Thank you! That means that Clojure is in better position here than Java 8+. There's https://github.com/cognitect-labs/transducers-java but it's not maintained anymore

tbaldridge17:12:45

@ghsgd2 I've written versions of that count that use very little memory, and some in other VMs (JS, PyPy) that are allocation free.

tbaldridge17:12:18

Basically instead of counting via a seq you can use reduce and add to the accumulator. So it's something like (reduce inc 0 coll)

tbaldridge17:12:33

(that's psudeo code though ^^)

New To Clojure17:12:04

@tbaldridge Actually I'm asking this to implement handling of map of vectors better. Looks like the best version would be to do reduce-kv which calls reduce in lambda function.

tbaldridge17:12:29

@ghsgd2 not sure I understand? How does this change with a map of vectors?

borkdude17:12:47

maybe he/she means map on vectors?

wiseman17:12:57

is there a nice clojure wrapper for the twitter api thatā€™s more up-to-date than https://github.com/adamwynne/twitter-api (which is missing media/upload)?

wiseman18:12:39

I checked twttr, which looks nice; it doesnā€™t support media/upload either, but it might be a nicer place to start for adding support for it.

wiseman00:12:26

In case anyone cares, Iā€™ve submitted a pull request to twitter-api that adds support for media/upload, including chunked upload, which means you can now post tweets with images, videos and animated gifs from clojure: https://github.com/adamwynne/twitter-api/pull/77

MiÄ·elis Vindavs18:12:18

I stumbled upon the fact that aset-int/`aset-long` are ~20x slower than aset. I googled around, but the best I could find was https://groups.google.com/forum/#!topic/clojure/pZB501dQwjk and http://www.brool.com/post/aset-is-faster-than-aset-int/ does anyone have a reasonable explanation as to whatā€™s going on?

New To Clojure18:12:40

@borkdude

(ns etl
  (:require [clojure.string :as str]))

(def values {1 (re-seq #"\w" "AEIOULNRST")
2 (re-seq #"\w" "DG")
3 (re-seq #"\w" "BCMP")
4 (re-seq #"\w" "FHVWY")
5 (re-seq #"\w" "K")
8 (re-seq #"\w" "JX")
10 (re-seq #"\w" "QZ")})

(defn transform  [dataset]  (->> dataset
       (reduce-kv (fn [result score letters]
                    (apply assoc result
                           (flatten (for [letter letters]
                                       [(str/lower-case letter) score])))) {})))

(transform values)
;; output:
{"a" 1 "b" 3 "c" 3 "d" 2 "e" 1
"f" 4 "g" 2 "h" 4 "i" 1 "j" 8
"k" 5 "l" 1 "m" 3 "n" 1 "o" 1
"p" 3 "q" 10 "r" 1 "s" 1 "t" 1
"u" 1 "v" 4 "w" 4 "x" 8 "y" 4
"z" 10}

New To Clojure18:12:48

that's what I'm talking about

New To Clojure18:12:55

doing transformation of map {score: [letter1..letterN], score2: [...],...} to map {letter1: score1, ..., letterN: scoreN,...}

New To Clojure18:12:14

i.e. from map of scores to list of letters to map of letter to score

New To Clojure18:12:10

@tbaldridge >not sure I understand? How does this change with a map of vectors? I mean it would be great to have some super-reduce which could handle nested structures. I still find functions new for me in Clojure.

MiÄ·elis Vindavs18:12:24

there is tree-seq and clojure.walk

borkdude18:12:58

can you give an example of the output? it doesnā€™t compile

tbaldridge18:12:55

Yeah, one thing that should be pointed out, is that seqs do allocation, and create garbage, but they are also really fast. Don't worry about using seqs at this point. You'll be surprised how fast something like for comprehensions work.

New To Clojure18:12:48

@mikelis.vindavs I'll try them. @borkdude It compiles, just requires clojure.string @tbaldridge I'll take your word for it. šŸ™‚ Speed is the main characteristic. And this method isn't processing gigabytes of data to worry about memory too much.

borkdude18:12:25

@ghsgd2 I got that, but I get ā€œDonā€™t know how to create ISeq from: java.lang.Longā€. Iā€™d rather have the expected output than the code.

New To Clojure18:12:26

@borkdude Sorry for that, added ns, corrected input values and added output to code above.

borkdude18:12:05

@ghsgd2 What if there are duplicates? 1 -> ā€œabcā€, 2 -> ā€œbxzā€ ?

New To Clojure18:12:57

@borkdude It's assumed that there are no duplicates.

New To Clojure18:12:37

just in case, re-seq here is just a shortcut for list of letters

borkdude18:12:19

no need to use re-seq there, you can handle strings as a seq in most functions

borkdude18:12:36

Hereā€™s how I would do it:

(defn transform  [dataset]
  (into {}
        (mapcat
         (fn [[k vs]]
           (for [v vs]
             [v k]))
         values)))

New To Clojure18:12:14

@borkdude Amazing, that's much better! Thank you a lot! @mikelis.vindavs, @tbaldridge Thank you very much too!

qqq20:12:35

1. : does NOT appear to be a valid keyword 2. (keyword "") returns : is this intended ?

arrdem20:12:38

Yes. clojure.lang.Keyword/intern does no validation. Not all possible keywords are readable.

arrdem20:12:59

Eg. (keyword "foo bar baz qux :/.")

reborg20:12:37

@mikelis.vindavs regarding https://clojurians.slack.com/archives/C03S1KBA2/p1513016238000281 aset-int (and similarly for the others) expands into something like:

(import '(java.lang.reflect Array))
(defn aset-int
  ([a idx v] (. Array (setInt a idx (int v))) v)
  ([a ^int idx v & vs] (apply aset-int (aget a idx) v vs)))
which uses Array reflection. Apparently, it's a long standing JVM issue to improve performance in this area (https://bugs.openjdk.java.net/browse/JDK-8051447).

MiÄ·elis Vindavs20:12:59

Thanks for the insight! Would be nice if that was mentioned in the docs. Is there any usecase for the aset-* functions over aset as it stands?

noisesmith20:12:04

@mikelis.vindavs I think it lets you skip coercing the argument you want to set I guess?

reborg20:12:38

I guess similar to what array reflection is used in Java, building arrays at runtime with the type dependent on some params

fabrao21:12:04

hello all, I have long waiting process in (->> ["" ""] (mapv #(future (ping-host %))) (mapv #(deref %))). How do I stop it in the middle of running?

ghadi21:12:34

you could deref with a timeout

fabrao21:12:18

how come? I want to force interrupt any time, not with timeout

fabrao21:12:37

is there any way?

seancorfield21:12:11

You can cancel a future.

fabrao21:12:45

but I have to own the future references?

ghadi21:12:25

a general good practice is to always enforce a timeout. it's better if you own the lifecycle of the future but not required

fabrao21:12:52

is there any cancel all?

noisesmith21:12:00

future-cancel can be iffy, though it exists

ghadi21:12:03

future-cancel or call java interop

noisesmith21:12:08

only certain methods are cancellable

noisesmith21:12:49

but most code you would want to cancel is eventually hitting a sleep or an IO op, and those are the cancellable things

noisesmith21:12:53

that reminds me I should figure out if a cancel will lead to a future exiting if it is doing a non-cancellable thing at that moment the cancel happens but then does a cancellable thing

fabrao21:12:05

yes, IĀ“m reading a socket

fabrao21:12:29

bot outside process can say stop to it

noisesmith21:12:34

but then you need the logic around when to future-cancel, and thatā€™s where the time out arg to deref is convenient

noisesmith21:12:57

if you have that logic already, sure, just hold onto the future and then cancel when needed I guess

fabrao21:12:32

ok, but how do I have the future references from here? (->> ["" ""] (mapv #(future (ping-host %))) (mapv #(deref %)))

fabrao21:12:59

change map to other looping function?

noisesmith21:12:07

no, the references are what gets passed to deref

noisesmith21:12:24

maybe you want a loop inside a future, where the future calls ping-host repeatedly

noisesmith21:12:52

what was the deref for, did someone need to consume the return value or wait for the pings?

fabrao21:12:11

wait for all the pings

noisesmith21:12:37

yeah, waiting for all the pings is weird if the pings are loopingā€¦

fabrao21:12:43

well, is there any way to pass reference futre itself to ping-host?

fabrao21:12:25

so I have all futures references and kill them all from outside

noisesmith21:12:22

maybe you want core.async or something - it sounds like your flow of data / execution design is still fuzzy?

noisesmith21:12:38

because if thereā€™s a loop in a future, what does someone wait on when waiting on a ping?

fabrao21:12:21

ItĀ“s in that way because I have to compare all the results with each other

noisesmith21:12:55

but if itā€™s a loop you have multiple results to compare -

fabrao21:12:13

the loop is only when waiting ssh session until ends

fabrao21:12:49

If I understand what you mean šŸ™‚

noisesmith21:12:58

so really what you want is one function that simultaneously opens an ssh connection and also loops on a ping, and then based on the ping time conditionally restarts the ssh connection?

noisesmith21:12:37

so one thread does (ssh host) and the other does (when (ping-takes-longer host reference) (restart-ssh host)) ?

fabrao21:12:23

this is happening already, I want to know how to stop it when itĀ“s runnig, any time I want

noisesmith21:12:23

so the thread you want to cancel isnā€™t the ping, itā€™s the ssh

fabrao21:12:13

IĀ“m saying ping and ssh because itĀ“s remote ssh ping

fabrao21:12:21

I have to ssh to some router/firewall and get the output ping from it

fabrao21:12:04

this is working already, but I have stop control from outside

fabrao21:12:24

that came from kill sign

noisesmith21:12:41

then store the future somewhere that the code that wants to cancel can call cancel-future on it?

fabrao21:12:33

yes, but how can I get the references of futres from that maps?

fabrao21:12:32

(->> ["" ""] (mapv #(future (ping-host %))) (mapv #(deref %))) using other function outside future?

fabrao21:12:56

(->> ["" ""] (mapv #(store-future (future (ping-host %)))) (mapv #(deref %))) ?

noisesmith21:12:21

you need a let block to get the future itself

noisesmith21:12:35

(let [hosts ["" ""] futures (mapv #(future (ping-host %)) hosts)] {:futures futures :results (mapv deref futures)})

noisesmith21:12:45

that way you can get results you can check, and also the futures

fabrao21:12:04

yes, you right, but I have to use some atom passed from outside to use it in other stoping function. Thanks alot

qqq22:12:14

(defn update-something [stack f]
  (conj (pop stack) (f (peek stack))))
how should I name this function? neither -last nor -first makes sense since stack can either be list or vector

qqq22:12:26

update-top ? is that the best?

the2bears22:12:09

or some combination of that with another word

wiseman00:12:26

In case anyone cares, Iā€™ve submitted a pull request to twitter-api that adds support for media/upload, including chunked upload, which means you can now post tweets with images, videos and animated gifs from clojure: https://github.com/adamwynne/twitter-api/pull/77