(I know about Typed Clojure; it doesn't seem to have won the hearts of all Clojurians)
(Or, do you just test the bejezus out of the thing?)
(I know about Typed Clojure; it doesn't seem to have won the hearts of all Clojurians)
(Or, do you just test the bejezus out of the thing?)
(defn persist-this-item [item]
{:pre [
(map? item)
(= (type (:item-name item)) java.lang.String)
(= (type (:item-type item)) java.lang.String)
(if (:created-at item)
(= (type (:created-at item))
org.joda.time.DateTime) true)
]}
(let [item (if (nil? (:created-at item))
(assoc item :created-at (tyme/current-time-as-datetime))
item)
item (assoc item :updated-at (tyme/current-time-as-datetime))
item (assoc item :_id (ObjectId.))]
(mc/insert "tma" item)))
So this function needs to be given some kind of map, and it needs to have 2 keys, :item-name and :item-type, and both of these need to be strings, and if :created-at is already set, then it needs to be a Joda DateTime.It's not accurate to say that Clojure has no types. It just gives you some flexibility about how strict you want to be.
I find that Clojure strikes a perfect balance: its flexibility with types allows me to easily integrate a lot of 3rd party tools without much work, but when I need to I can be as strict as a I like with types.
Edit to add: wow, the code is ugly on Hacker News. Funny that this site provides no tools for posting code snippets, given the subject.
But that said, :pre/:post w/ unit tests seems pretty powerful. You can assign the invariants to the functions themselves and have a better / more robust set of assertions in your unit tests.
Yes one thing you might use :pre/:post for is type checking, but it can do more than just that.
Well, it would be cooler if the compiler could check those contracts at compile-time and issue some helpful warnings, but at runtime they are still valuable because the code will fail sooner rather than later. Plus you can probably disable them completely, should you experience problems with performance in production.
They also serve as documentation for other developers, documentation that you're forced to keep in sync. This documentation is not about the actual business logic, that ends up being laid out in tests, but rather about interface specifications and invariants.
So there you have it - testing serves a different purpose.
def increment(n):
assert isinstance(n, int) or isinstance(n, float)
result = n + 1
assert isinstance(result, type(n))
return result
So as long as we can agree on that much, then we can agree that there is a value to being judicious about where you should and shouldn't use type assertions.By the way I'm a fan of static typing as well, although I see value in dynamic languages too. And I definitely acknowledge the limitations of unit testing, although from a practical point of view, a comprehensive set of unit and functional tests is usually robust enough. And, of course, type systems don't make any guarantees against logic errors. :)
On your example, you're of course right. I'm also not a fan of checking the actual type in a dynamic language, since it defeats the purpose of it being dynamic. I like assertions that are more useful than that, like:
def sqrt(x):
assert x >= 0, "only defined for positive numbers"
last_guess = x / 2.0
while True:
guess = (last_guess + x / last_guess) / 2
if abs(guess - last_guess) < .000001:
return guess
last_guess = guess
Now clearly this helps, since it aids in readability (this function is defined for positive numbers only) and if you call it with a negative number, it will loop forever. Clojure has a variable called "*assert*" just for this
purpose: set it to "false" for production to
turn off all these type checks.If you're dead set on adding type declarations, perhaps you'd be better off with Haskell or something?
- its hard to safely refacture without static types (and the help of the IDE that often comes with it)
- dynamic languages must compensate the missing type checking support from the compiler with additional unit tests, negating the productivity gains
Sometimes it would be nice to have both worlds in the same language, but the way Clojure does it does't appeal to me at all.
The idea: add static types to Clojure without compromising on the dynamism / flexibility / convenience
Indent code by at least 4 spaces. (Just like in Markdown in e.g. Stack Overflow.)
like
this
and indent some moreI have a friend who is a strong typing devotee who has worked heavily in Clojure for a couple of years, and he curses the lack of strong typing - but he's comparing to the likes of Haskell and OCaml. As a dynamic typing enthusiast myself, I'm comparing to Ruby, and Clojure is definitely superior in terms of how much it needs to be tested.
What drew me to Clojure over Ruby, though, was actually a matter of data modeling. I'm working with a graph database (Neo4J), and I found that good graph technique is terrible OO technique. I was violating the Law of Demeter constantly, reaching through objects to get to other objects, passing internal state around with the express intent of mutation, etc. Switching to a functional paradigm lined up perfectly with good graph design. No more smells! No more pain! And again, a lot less testing, because I'm no longer fighting against the nature of my data.
But if you actually have facilities to pass around and manipulate those kinds of data structures in a way with no side effects, it's trivial to verify (first on the REPL, then in your unit tests) that whatever transformation you're writing applies properly down the entire nested chain, and it will always work for anything of that pattern. And it is much quicker to write those kinds of transformations iteratively in a dynamic language than trying to represent it on a type level a la Scala.
I can't imagine having to go back to Java collection classes again. Ugh.
Strange...
I haven't used Typed Clojure, so I can't speak to that, but if you want to do type-level programming Clojure is probably not your weapon of choice.
On top of all that, sane defaults like immutable data, lack of mutable locals, and sane concurrency primitives means that a whole class of bugs just don't exist in normal Clojure code. All this sums up to a codebase that really doesn't need static typing.
I write Clojure code for a living, and have worked on some really large Clojure codebases, and yet I've never felt the need for a type system. It's a fallacy that large systems have to be so complex that only a type system will save you. Often the better route is to simplify and modularize the system.
It's still saner than, say, Ruby.
For others, Clojure wouldn't help at knowing whats going on ;).
(:somekey (cheshire/parse-string
(slurp "/tmp/somefile.json") true)
Or (-> "/tmp/somefile.json"
(slurp)
(cheshire/parse-string true)
(get :somekey))
In other words, there's much nicer ways to write maintainable, readable code that you can still understand in the morning.Your "jumping around" comment also makes me wonder how you were laying out your functions? My advice is to go through the libraries of the popular Clojure libraries, you will discover a lot of good practices. It's taken me a few months of Clojure to start writing cleaner, readable code.