Just Juxt #18: Black Box Testing (4clojure #65)
Clojure has many sequence types, which act in subtly different ways. The core functions typically convert them into a uniform "sequence" type and work with them that way, but it can be important to understand the behavioral and performance differences so that you know which kind is appropriate for your application.
Write a function which takes a collection and returns one of
:map
,:set
,:list
, or:vector
- describing the type of collection it was given. You won't be allowed to inspect their class or use the built-in predicates likelist?
- the point is to poke at them and understand their behavior.
Here is the full list of functions that we can't use:
class
type
Class
vector?
sequential?
list?
seq?
map?
set?
instance?
getClass
(ns live.test
(:require [cljs.test :refer-macros [deftest is testing run-tests]]))
(defn black-box [c]
)
(deftest test-65
(is (= :map (black-box {:a 1, :b 2})))
(is (= :list (black-box (range (rand-int 20)))))
(is (= :set (black-box #{10 (rand-int 5)})))
(is (= :vector (black-box [1 2 3 4 5 6])))
(is (= [:map :set :vector :list] (map black-box [{} #{} [] ()]))))
(run-tests)
Luckily, there are a few friends still left to help us out:
user=> (doc associative?)
-------------------------
clojure.core/associative?
([coll])
Returns true if coll implements Associative
Vectors and maps are associative:
(associative? [1 2 3])
(associative? {:a 1 :b 2})
But lists, sets and strings are not:
(associative? '(1 2 3))
(associative? #{:a :b :c})
(associative? "fred")
user=> (doc reversible?)
-------------------------
clojure.core/reversible?
([coll])
Returns true if coll implements Reversible
An empty vector is reversible:
(reversible? [])
But an empty list is not:
(reversible? '())
Sets and maps are not reversible:
(reversible? {})
(reversible? #{})
But sorted ones are:
(reversible? (sorted-map))
(reversible? (sorted-set))
user=> (doc ifn?)
-------------------------
clojure.core/ifn?
([x])
Returns true if x implements IFn. Note that many data structures
(e.g. sets and maps) implement IFn
An anonymous function is a function as you'd expect:
(ifn? #("my anonymous function"))
Is a vector a function?
(ifn? [1 2 3])
Sure is, lets call it:
([1 2 3] 0)
Maps and sets are also functions.
A number is definitely not a function:
(ifn? 1)
But a symbol is:
(ifn? 'foo)
And so is a keyword:
(ifn? :foo)
Juxtification:
Using juxt
with these functions returns a vector of properties by which to identify our culprit. Then we can make a simple matrix to look up what it is:
(defn black-box [c]
(case ((juxt associative? reversible? ifn?) c)
[true true true ] :vector
[false false false] :list
[true false true ] :map
[false false true ] :set))
(run-tests)