7 ms·
I recently learned enough Standard ML to be dangerous, and I gained enough of an appreciation of ML-style static / type inferred languages to see where the auth
by postfuturist 15y ago
I recently learned enough Standard ML to be dangerous, and I gained enough of an appreciation of ML-style static / type inferred languages to see where the author is coming from. I even agree to some level.
But, in practice, some dynamic languages just don't seem to suffer from hidden type bugs. One of those is Clojure. Instead of packing everything into complicated types, you are mostly working with maps and sequences of maps--just raw data--with higher order functions. There isn't a lot of room for type bugs. Additionally, without any input from the developer, Clojure does some type-inference all on its own. You can enable warn-on-reflection such that the Clojure compiler will tell you anywhere in the code where it's going to have to use reflection to figure out how to invoke a Java method or property on an object. You can fix them with type hints such that you get performance on par with statically typed Java.
Clojure protects you from name typos you might see in other dynamic languages and accidentally clobbering existing variables through the fact that most variables are really not variable at all. You bind a value once in a lexical scope and it can't be mutated. That's more of a benefit of functional languages and lexically scoped languages, than statically typed ones.
It may boil down to a matter of taste, but I don't see the need for fancy refactoring tools like "method extraction" in a language like Clojure. The methods already don't live in classes, so they hardly need to be extracted from them :). If you rename a method in Clojure, the compiler _will_ complain in all the places where you are trying to call a method that no longer exists.
- pron 15y agoYep. Same here - I had always thought I agreed with all the arguments mentioned in the post, and then I found Clojure. I'd like to add that I don't know if there are any existing refactoring tools for Clojure, but there certainly could be. All the things that you might want to rename safely - say functions and keywords (used for hashmap access, and serve as sort of Clojure class fields) are proper symbols, well defined in a namespace. You could have a tool that renames them safely. In Scala, OTOH, a statically typed language, you can't even rename methods safely because they're not always referenced through their defining types, as they could be called through an injected so called "structural type". So nowadays, some statically typed langiuage lose, through cleverness, many of the advantages of static typing, while dynamically typed languages, through disciplined restraint, manage to gain them. EDIT: actually, the one thing I miss most from statically typed languages is the immediate error highliting in the IDE. The REPL alleviates the need for that, but it was still cool immediately seeing files and lines highlighted in red as soon as I type in some error. I wish Clojure could do that.
- eta_carinae 15y ago> I don't know if there are any existing refactoring tools for Clojure, but there certainly could be. Maybe, but they would be imprecise and they would require developer supervision. When the tool doesn't have any types, it can't reason about the code accurately. Here is an article that explains why in more depth: http://beust.com/weblog/2006/10/01/dynamic-language-refactoring-ide-pick-one/ http://beust.com/weblog/2006/10/01/dynamic-language-refactor...
- igouy 15y agoNow read comments #30 #31 and #32.
- gruseom 15y agoIndeed. They refute the claim in the post that search-and-replace was "the best [Smalltalk] could do". The author ought to have corrected his post, but instead just ignored this.
- igouy 15y agoPerhaps the author follows the never let facts get in the way of a story tradition :-)
- deleted 15y ago[deleted]
- quatrevingts 15y agoNo, what Scala is giving you is the choice: use structural types, and sacrifice some of the benefits of nominal types, or don't. Note that if you perform your renaming and now class C no longer matches structural type S, any part of the code that depended on that typing will no longer compile: safety is preserved. Structural types therefore only make renaming slightly less automatic. (And they're rarely used anyway.) I'm also skeptical of your claim about Clojure symbols: can you not select a field using a runtime-computed values, thereby making perfect static refactoring intractable?
- quatrevingts 15y agoI find that when working with maps and sequences of maps with higher-order functions, types are especially critical to ensure that e.g. you didn't get your arguments backwards in your fold, or forget that your map values are sequences, not single elements. So much can be accomplished with a chain of higher-order invocations, and so much can go wrong (especially if you have Perl-style type coercions), but types are great at catching the errors. At the same time, I think that programing with complex generic structures is a functional anti-pattern anyway: if your type is "Map (Foo, Bar) (Map Int (Set String))", and it's not somehow encapsulated (as in a class or ADT), good luck. In Python you would hide this in a class, but what is the Clojure solution (genuine question)?
- Tuna-Fish 15y agoThe Clojure solution is a map of maps. There are facilities for creating types (notably, defrecord), but it's just a way to optimize your code -- the interface it offers is exactly the same as a map, to the point that if you call assoc on it (as in, add a key-value pair) you get a normal map back. This really doesn't appear to be as much of a problem as it was for me in Python and Haskell. Perhaps it's because the accessor syntax "feels right" so that manipulating even complex structures of maps and vectors seems natural, and the code won't look too convoluted. (In Clojure, maps are functions of their keys, vectors are functions of their indexes. So: (def a-map {:a 1 :b 2}) (a-map :b) => 2 also, when using keywords as keys, the reverse also works: (:b a-map) => 2 This basically makes normal operations on maps look much like using accessors on Haskell ADTs.)
- quatrevingts 15y agoThat doesn't sound like encapsulation though. If I have some complex map of maps with invariants I need to maintain, is there any way to ensure that client code doesn't mess with it?
- Tuna-Fish 15y agoThe Clojure ethos is quite anti-encapsulation. Generally, you maintain complex invariants by defining the functions that manage your data and not updating it except through them. (This is easier than it sounds, because when all data structures are immutable, drive-by-mutation just doesn't happen.)
- Darmani 15y agoI spent much of a week debugging an issue in a Clojure multiplayer game where the same action was being handled differently on different machines. Somehow, the "true" branch of an if-statement was being taken even though the condition was "false." The reason? When the action was sent over the network, the boolean was converted to a Boolean. On the same project, I would also frequently suffer from name-typoes -- in structure keys (deftype and defrecord didn't exist yet). A dynamically-typed language is still dynamically typed. A lot of basic analyses are still undecidable. Lexical scoping doesn't fix that.
- gruseom 15y agoWhen the action was sent over the network, the boolean was converted to a Boolean. That sounds more like an impedance mismatch between Clojure and its underlying platform.
- Ralith 15y agoAnd yet strong typing would have identified the issue immediately.
- gruseom 15y agoHow do you know that? Programs in statically typed languages can still have bugs reconstituting objects serialized over a network.
- Darmani 15y agoIn a statically-typed language, I couldn't have used a Boolean as the condition of an if-statement.
- jmount 15y agoI like the point. And not being able to use if(0) (as in C) has caught tons of bugs (like writing "if(x=0)" instead of "if(x==0)"). There are ton of things that sounded convenient in the early history of programming languages that are really anti-patterns. For example: Confusing ints with booleans. Confusing ints with pointers (no longer done, thank goodness). allowing assignments to have a non-void type ("x=1.0" should be of type void, not type double). Poor choice of assignment and test equals (would be nice not to use "=" but to force ":=" and "=="). Pverly aggressive type coercion and autoboxing. Integer division using the same symbol as floating point (many end-users want 5/2 to be 2.5 as the answer, mostly you want it to be 3 if you are doing loop-foo). Making "else if" and if-braces mere convention. Allowing null way too many places. Automatic variable decleration.