Introduction
One of the things that has consistently been difficult in the whole dynamic typing/static typing conversation is that people don’t seem to understand what a real static typing language can do: here’s a classic example (and someone else who was also annoyed). The dynamic typing vs. static typing conversation seems to be Java’s type system vs. Ruby’s type system, which simply isn’t fair. So, in the spirit of advancing discourse and helping people understand why I enjoy Ocaml so much, let me present…
7 Actually Useful Things You Didn’t Know Static Typing Could Do
Run Without a Distinct Compilation Step
Perhaps the most useful tool in the Ruby coder’s toolbox, the Read-Evaluate-Print Loop (REPL) called “irb” is the sketchbook where your day-to-day code artist tries out new efforts and exercises the language. It’s also the place where you go to start digging through implementation reflection to see what methods you really have, and how those methods actually behave. That method that squares the integer passed in — what happens when you pass in a string? Those kinds of things.
An analogous tool is also provided for Ocaml developers. If you just call the “ocaml” program without an argument, it drops into a REPL — referred to as the “toplevel” — where you can try things out and do all the same kinds of rapid development practices.
The toplevel produces feedback like this:
# let input () = print_string "This is output!\n";;
val input : unit -> unit = <fun>
# input ();;
This is output!
- : unit = ()
(I’m going to be using toplevel printouts later on in the article, so you’re going to want to get used to seeing them: the # denotes the start of input. Blue denotes standard output. Red denotes REPL feedback (typing information).)
Similarly, an Ocaml source file can be executed directly. When run this way, it still provides type safety, and this is a nice way to check your code out as you’re building it. Personally, I’m not a big fan of Ocaml as a scripting language (Perl still wins for me, even over Ruby), but Brian claims it holds its own: “as gigawatt lasers go, this one works remarkably well for killing flies.”
Make NullPointerExceptions/nil-when-you-didn’t-expect-it a Thing of the Past
That’s right. If you switch to Ocaml, the compiler will guaranty that you never see a $!# NullPointerException (or equivalent) again. Ever. Really.
Null/nil is so obnoxious and pervasive in Java and the dynamic languages that Groovy introduced the “safe navigation operator” (?.) and the “Elvis operator”. Ruby hasn’t come to a final decision yet, but they’re constantly digging around at how this should work (someone want to provide cites?). Perl and Groovy also hijack boolean coercion to make a null object equal to false, which opens up for the common bug where an int value is 0 and so gets eaten in what was supposed to be a null check. Back in Perl, people discovered that “0e0″ evaluated to true and started returning that freak of floating point arithmetic to mean “zero-but-true” to avoid the coercion (example). This, of course, just leads to the new bug of people using “unless $var” to actually check for 0 and having it fail on them (example with hacky code). Irony, thy name is operator overloading.
Ocaml saves us from this Hell by reversing the default behavior that you see in C++/Java/Perl/Ruby: instead of variables being nullable by default, you have to explicitly declare that this variable you’re using is possibly null (called “None”). If you’ve been declaring variables at the point of assignment, as I’ve been advocating ([1], [2], [3]), then you already know that you can usually get rid of null without missing it much.
In those cases when you do miss it in Ocaml, you can use the “`a option” type. Expressions of that type may or may not have a value, and the user code is required to figure out what to do in either case. This is what it looks like:
# let x = Some(1);;
val x : int option = Some 1
# let y = None;;
val y : 'a option = None
# let foo x = match x with Some(i) -> print_int i; print_string "\n" | None -> print_string "None provided!\n";;
val foo : int option -> unit = <fun>
# foo x;;
1
- : unit = ()
# foo y;;
None provided!
- : unit = ()
This pattern makes the API a bit more obnoxious to use, which actually means that possibly null variables are used quite a bit less in Ocaml in other languages. This is to be construed as a Good Thing: having the compiler guaranty that your objects aren’t null means that you don’t have to spend all your time writing “newvar = var unless var” or other (usually redundant) safety code. And it means that you don’t have to either waste your typing in the documentation explaining what happens when a null pointer is passed in, or leave your code to behave arbitrarily in that case.
Duck Typing
This is the big feather in the dynamic language enthusiast’s cap: duck typing. Not having to explicitly type every variable and parameter and return value is really nice. It makes your code a lot easier to read by increasing the signal to noise ratio: redundant tokens can be knocked out of the code, and just the core logic is left behind. It also enables that wonderful productivity boost of surprising reuse in more places: you don’t have to be a Java coder for too long before you discover a place you’d really like to pass in a similar object, but can’t because they don’t have the exact same type.
Ocaml allows this same duck typing gains as the dynamic languages without sacrificing the type safety. Objects, including parameters and return values, are structurally and not nominally typed: methods require the parameters to implement “#foo()”, “#bar(int)”, “#baz(string, list)” instead of requiring the parameters to belong to a certain element of the type tree which has “#foo()”, “#bar(int)”, and “#baz(string, list)” as part of its API contract. Even better yet, Ocaml will derive the required structure from the method implementation itself, so the developer doesn’t need to specify anything.
Here’s what it looks like in play. Consider this Ruby code, written by someone who decided to ignore the whole comparison/equality inheritance problem by using duck typing:
def nudge(pt) pt.move(1, 1) end class TwoDeePoint attr_accessor :x, :y def initialize @x = 1 @y = 2 end def move(dx=1, dy=1) @x += dx @y += dy end def put print "(#{@x}, #{@y})" end end class ThreeDeePoint attr_accessor :x, :y, :z def initialize @x = 3 @y = 4 @z = 5 end def move(dx=1, dy=1, dz=1) @x += dx @y += dy @z += dz end def put print "(#{@x}, #{@y}, #{@z})" end end class Whatever def put print "Whatever..." end end def do_put(obj) obj.put() puts end two_d = TwoDeePoint.new do_put two_d three_d = ThreeDeePoint.new do_put three_d whatev = Whatever.new do_put whatev nudge two_d two_d.put nudge three_d three_d.put puts
Lots of cool duck typing going on there. Here’s the Ocaml version:
let nudge pt = pt#move2 1 1 class two_dee_point = object val mutable x = 1 val mutable y = 2 method move dx = x <- x + dx method move2 dx dy = x <- x + dx; y <- y + dy method put = print_string ("(" ^ (string_of_int x) ^ ", " ^ (string_of_int y) ^ ")") end;; class three_dee_point = object val mutable x = 3 val mutable y = 4 val mutable z = 5 method move dx = x <- x + dx method move2 dx dy = x <- x + dx; y <- y + dy method move3 dx dy dz = x <- x + dx; y <- y + dy; z <- z + dz method put = print_string ("(" ^ (string_of_int x) ^ ", " ^ (string_of_int y) ^ ", " ^ (string_of_int z) ^ ")") end;; class whatever = object method put = print_string "Whatever..." end;; let do_put obj = obj#put; print_string "\n";; let two_d = new two_dee_point;; do_put two_d;; let three_d = new three_dee_point;; do_put three_d;; let whatev = new whatever;; do_put whatev;; nudge two_d;; two_d#put;; nudge three_d;; three_d#put;; print_string "\n";;
There’s a lot more conversation about this in the comments below. And here’s an interesting little statistic courtesy of our buddy wc -l:
42 duckTyping.ml
68 duckTyping.rb
DSLs
The whole idea behind DSLs is one that I’ve never really gotten my head around. When they first came out, the promise was that they would relieve developers of all that dreary per-instance configuration by allowing business people to jump in and write code directly. This is something which set of my snake oil alarms, because I had heard it before about Java bean-based GUIs (“draw a line from the output of this bean to that input of that bean — that simple!”) and graphical scheduler front-ends (“create a box at this schedule, and then type in the name of the thing you want to run — that simple!”).
So far, my cynicism seems to be validated: asking around now, it seems like the definition of “DSL” is starting to resemble the definition of “API” — and I’m not the only person who has noticed[1]. This kind of API — where you say what you mean instead of building up whole towers of new DoIt(foo, bar) and new ParamHolder(baz, frodo) structures — is definitely an advancement, and dynamic languages like Ruby and Groovy are right to be proud of the ease of their use.
But while this is an advance, it’s not a novel invention — there is already prior art (see another reinvention here). What the Rubyists call a “DSL”, Ocamlists call “readable code”[2]. Ocaml provides two very powerful tools for writing DSL-esque code simply and easily: variant types and matching.
Consider this implementation of Rail’s Numeric Time extension:
# #load "unix.cma";;
# open Unix;;
# type scale = Hour | Hours | Day | Days | Week | Weeks;;
type scale = Hour | Hours | Day | Days | Week | Weeks
# type direction = Ago | Hence;;
type direction = Ago | Hence
# let date x y z =
let seconds_per_hour = 60.0 *. 60.0 in
let scl = match y with
| Hour -> seconds_per_hour
| Hours -> seconds_per_hour
| Day -> 24.0 *. seconds_per_hour
| Days -> 24.0 *. seconds_per_hour
| Week -> 24.0 *. 7.0 *. seconds_per_hour
| Weeks -> 24.0 *. 7.0 *. seconds_per_hour
in
let amt = match z with
| Ago -> -1.0 *. scl *. (float_of_int x)
| Hence -> scl *. (float_of_int x)
in
localtime (gettimeofday() +. amt)
;;
val date : int -> scale -> direction -> Unix.tm = <fun>
# let print_datetime t =
print_int t.tm_mday;
print_string "/";
print_int (1 + t.tm_mon);
print_string "/";
print_int (1900 + t.tm_year);
print_string " ";
print_int t.tm_hour;
print_string ":";
print_int t.tm_min;
print_string ":";
print_int t.tm_sec;
print_string "\n"
;;
val print_datetime : Unix.tm -> unit = <fun>
# let example1 = date 5 Days Ago;;
val example1 : Unix.tm =
{tm_sec = 11; tm_min = 17; tm_hour = 9; tm_mday = 9; tm_mon = 3;
tm_year = 108; tm_wday = 3; tm_yday = 99; tm_isdst = true}
# let example2 = date 1 Hour Hence;;
val example2 : Unix.tm =
{tm_sec = 34; tm_min = 17; tm_hour = 10; tm_mday = 14; tm_mon = 3;
tm_year = 108; tm_wday = 1; tm_yday = 104; tm_isdst = true}
# print_datetime example1;;
9/4/2008 9:17:11
- : unit = ()
# print_datetime example2;;
14/4/2008 10:17:34
- : unit = ()
I haven’t done anything cute to get around having to put the word “date” in front of the numbers, but that’s more because I like how it reads: “let example one be the date five days ago.”
[1]“Have you ever programmed in a language other than Ruby? (PHP and HTML don’t count.) If not, it’s a DSL.” ROFLMAOPIMP!
[2]Not to be confused with literate progamming.
Passing Blocks Without Pain
For some reason, there is a sense (sometimes explicit) that static languages can’t handle closures — that closures and functional programming are uniquely the domain of dynamically typed languages, and that statically typed languages are struggling to offer any kind of real support.
Despite that popular untruth and Ruby supporter’s touting of Ruby as “a sort of OO / functional hybrid” (cite), this is one of the big pain points in Ruby, and there’s a lot of experimentation to figure out how to fix it. For why this is pain in Ruby, check out Paul Cantrell’s excellent “Closures in Ruby” — for checking out approaches to fix the problem, see the experimental Ruby 1.9 syntax extensions here, here, and here. In short, it all tracks back to Ruby not having first-level functions, and therefore not being a “functional/OO hybrid”, as much as it may want to sell itself as such. It’s aggravated by the fact that blocks and Procs are different things, which causes a lot of pain as demonstrated here:
puts "General function passing demonstration"
def partition(lst, &check)
yes = lst.select(&check)
no = lst.select { |item| !(check.call(item)) }
return [yes, no]
end
lst = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
puts "List:\t#{lst.join("\t")}"
out = partition(lst) { |item| item % 2 == 0 }
yes = out[0]
no = out[1]
puts "Yes:\t#{yes.join("\t")}"
puts "No:\t#{no.join("\t")}"
puts "Nontrivial closure demonstration"
def mod_closure_maker(mod)
return Proc.new { |item| item % mod == 0 }
end
# NOTE: out = partition(lst, mod_closure_maker(3)) doesn't compile
# closures.rb:25:in `partition': wrong number of arguments (2 for 1) (ArgumentError)
def partition2(lst, check)
partition(lst) { |item| check.call(item) }
end
mod3 = mod_closure_maker(3)
mod4 = mod_closure_maker(4)
out = partition2(lst, mod3)
yes = out[0]
no = out[1]
puts "--0 Mod 3--"
puts "Yes:\t#{yes.join("\t")}"
puts "No:\t#{no.join("\t")}"
out = partition2(lst, mod4)
yes = out[0]
no = out[1]
puts "--0 Mod 4--"
puts "Yes:\t#{yes.join("\t")}"
puts "No:\t#{no.join("\t")}"
# NOTE: out = partition2(lst) { |item| item % 5 == 0 } doesn't compile
# closures.rb:46:in `partition2': wrong number of arguments (1 for 2) (ArgumentError)
# This is the way to handle that partition
def partition3(lst, check_proc=nil, &check_block)
if(check_proc)
partition2(lst, check_proc)
else
partition(lst, check_block)
end
end
So, by way of some optional-argument hackery, we can get to the point where we can either pass a block or a Proc. Or both, really, but the Proc will be ignored if the block is provided. And if you want to pass two blocks to a method in Ruby, you’re SOL.
Ocaml, being a real functional language, handles this with grace.
let puts x = print_string x; print_string "\n";;
let rec puts_just_list lst =
match lst with
| [] -> ()
| x :: xs -> print_int x ; print_string "\t"; puts_just_list xs
;;
let puts_list title lst = print_string title; print_string "\t"; puts_just_list lst; print_string "\n" ;;
puts "General function passing demonstration\n";;
let partition lst check = List.partition check lst;;
let lst = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10];;
puts_list "List:" lst;;
let out = partition lst (function item -> item mod 2 = 0) in
let yes = fst out in
let no = snd out in
puts_list "Yes:" yes;
puts_list "No:" no
;;
puts "Nontrivial closure demonstration"
let mod_closure_maker x = function y -> y mod x = 0
let mod3 = mod_closure_maker 3;;
let mod4 = mod_closure_maker 4;;
let out = partition lst mod3 in
let yes = fst out in
let no = snd out in
puts "--0 mod 3--";
puts_list "Yes:" yes;
puts_list "No:" no
;;
let out = partition lst mod4 in
let yes = fst out in
let no = snd out in
puts "--o mod 4--";
puts_list "Yes:" yes;
puts_list "No:" no
;;
Succinct Data Structure Syntax
Dating back to Perl, one of the major advantages that mainstream dynamic languages brought to the table over C++/Java is the succinct syntax for data structures. Data structures make up the backbone of data organization, and so the fact that they’re second-class syntactical denizens is really a shame. Despite having learned Lisp first (for Emacs hacking), my experience was that it was Perl’s succinct syntax which really revealed the power of having lists of maps of maps of lists of objects. The answer from the Object Oriented crowd was that each of those layers should have been layered and abstracted out as objects, but that gets really chatty really fast.
Thankfully, though, you can have your cake and eat it, too. With Ocaml, you have first-level data structures that provide the same level of power and succinctness as Perl or Ruby while still providing type safety.
Lists are the easiest to demonstrate, and the most familiar to the Ruby/Perl coder. They just look like this: ["item 1"; "item 2"; "item 3"]. Since those are all strings, Ocaml interprets this list to have a type of “string list”.
Experiment with Syntax
It’s a common conceit of dynamic language enthusiasts that the lack of type safety and fast-and-loose syntax maneuvers is actually a good thing — not just because it enables some patterns that aren’t viable a static language, but because it encourages exploration:
Lispers talk about Bottom-Up Programming. Well, dangerous features enable bottom-up language evolution. We discovered we like Symbol#to_proc because it bubbled up from the bottom. Someone invents something. If other people like it, they use it. The word gets around. People improve on it. Eventually it gains acceptance and becomes the de facto way to write code.This is true in all languages, but languages—like Ruby—that include dangerous features give the fringe a broader latitude to invent new things. Of course, they also break things and they invent stupid things and they get excited and write entire applications by patching core classes instead of writing new classes and commit all sorts of sin. (Raganwald, from (1..100).inject(&:+))
Ocaml provides the ability to screw around with syntax and experiment with new language constructs — in fact, it’s one of its key purposes for existing. It enables that through a meta-language system called “camlp4“/”camlp5“, which acts as a pre-processor to your source code. The advantage of this is that you are producing backwards compatible code with all the same guaranties and reliability as the rest of the language — once I compile my library, the fact that I used list comprehensions and string interpolation preprocessors doesn’t matter.
This is certainly a leap and a bound beyond global duck punching, where you need to know how every use of that module is going to behave, and then validate that it works out in all those cases. The reality in my experience is that people who pull off global duck punching tend to just kind of pray and hope their unit tests catch any bug they just introduced. This makes me unoptomistic about the maintainability of that code — and even the Ruby community is starting to agree with me (cite, cite).
Conclusion
If you have previously knocked static languages, I hope that you won’t trot out false statements like “static typing sucks because it doesn’t do duck typing” or “static typing slows down your development because you have to compile your code all the time”. If you were a static language fan who was clinging to Java/C#’s type system, hopefully you see that there’s a lot more out there than those languages allow.
This post started out as “5 of Ruby’s Greatest Hits in Ocaml”, and it’s a testament to the language that Ocaml stole the spotlight with things that Ruby can’t do at all. This is just the tip of the iceberg with functional programing and the way that it warps your mind, so I really hope you dig deeper into it.
68 Comments
He of the Unicorns referenced me to “What To Know Before Debating Type Systems“, which is also good and covers some different ground than this article.
Wow thanks, this is a very valuable contribution to the discussion Dynamic vs Static as it clearly shows that the lack of features in static typing languages which are available in the ruby/dynamic land is much more an implementation issue than an inherent property of static typing languages.
Awaiting Raganwald’s response…
Johannes
“Awaiting Raganwald’s response”
I moved back in time to respond:
http://weblog.raganwald.com/2007/07/can-your-type-checking-system-do-this.html
At the moment, I personally value Ruby’s particular set of trade-offs and compromises more highly than Java’s particular set of trade-offs and compromises. That says nothing about my opinion of type systems, provability, or OCaml.
Come to think of it, I have repeatedly advised people to read “The Little MLer.” I wonder why.
Note: I fixed a false assertion about Ruby (0 == true). That’s just Perl, I guess.
And Groovy, it looks like.
0 != true in Ruby. However, 0 is “truthy”:
irb(main):001:0> !!0
=> true
irb(main):008:0> 0 ? ‘true’ : ‘false’
=> “true”
What do I think of this? Well, I actually don’t like it, but not because I’m dogmatic about whether 0 ought to be truthy. Or whether the empty string ought to be truthy. Or whether the empty list ought to be truthy.
I don’t like the fact that “truthiness” isn’t “turtles all the way down.” if I invent my own class–say for permissions–there is no way to specify that one of the instances of that class ought to be falsy. There is no way to set it up so that I can write:
if allowed_to_proceed…
And have allowed_to_proceed be an instance of my permissions class instead of plain true and false.
“NOTE: out = partition(lst, mod_closure_maker(3)) doesn’t compile”
well… out = partition(lst, &mod_closure_maker(3)) compiles just fine.
“And if you want to pass two blocks to a method in Ruby, you’re SOL.”
You mean something like
def foo(arg, blk1, blk2); … end
foo(some_arg, lambda { … }, lambda { … })
?
Reg, maybe you could extend the if statement instead. (Untested code)
; most classes are not truthy
(defmethod truthy-p ((object t)) (values nil nil))
; some-type is truthy, but most values are false
(defmethod truthy-p ((object some-type)) (values nil t))
; this is a true object for some-type
(defmethod truthy-p ((object (eql true-object))) (values t t))
(defmacro my-if (condition then else)
`(let* ((result ,condition))
(if (not result)
,else ; nil is always false
(multiple-value-bind (is-true has-truthy) (truthy-p result)
(if (or (and is-true has-truthy)
(not has-truthy))
,then
,else ; removing this code duplication is left as an exercise
)))))
“The whole idea behind DSLs is one that I’ve never really gotten my head around.”
Actually the biggest problem when I started to learn DSL is that 10 different people will have 21 different definitions of DSLs.
You will find people that refer to DSLs as nice looking APIs, then you find people that say even data that is specific do a certain domain is a DSL …
I think we should focus on what a good DSL is:
- human readable
- expressive (ie with few words you can express a lot)
For me, the best DSL are those that can be used in old games like zac mc kraken to completely script the game logic.
So for me it is some kind of language that is easier than a programming language but very specific to a problem _AND_ very human readable
“This “advancement” is something that Ocaml already invented (see another reinvention here). What they call a “DSL”, we call “readable code”[2]. Ocaml provides two very powerful tools for writing DSL-esque code simply and easily: variant types and matching.”
Quick correction: Ocaml didn’t invent DSLs. DSLs have been around since Lisp. As far as Ruby goes, the creators of Lisp and Smalltalk invented everything of significance they have (and everything virtually any other dynamic language has).
“As far as Ruby goes, the creators of Lisp and Smalltalk invented everything of significance they have (and everything virtually any other dynamic language has).”
Yes, in exactly the same sense that the Macintosh introduced nothing new in 1984 and the iPod introduces nothing new now. Matz has been very open about the basis of Ruby, he has called it “MatzLisp” and also been very open about apeing Smalltalk’s object system.
Personally, I value *design*, the particular choice of which things already invented belong together and how to blend them into a cohesive whole. Is Ruby’s design better than Smalltalk’s design or Lisp’s design? I don’t know, so far I still prefer Scheme :-)
@Marc
You’re right: I’ll clean up the language. I didn’t mean to imply that Ocaml was the originator of readable code, but rather that what is being touted as a novel invention is really rehashing things that already exist.
@she
I’m not really getting a lot out of your “focus” — I think the problem is that what you consider “human readable” and what I consider “human readable” may be drastically different. Is “date 3 days ago” an API or a DSL? What about “3.days.ago”? What about Entity.find(:all, :conditions => {:attr1 => “foo”, :attr2 => “bar”})? Each of those is human readable for some definition of “human”.
Maybe it’d help if you could tell me what are some examples of “good” DSLs and some example of things that are DSLish, but are really APIs.
@ste
No. You’re passing two lambdas in, which is similar to passing two Procs in: lamdba != block. See “Closures in Ruby” (linked to in the blog post).
I think the title of your article is a little misleading…it isn’t about what static typing can do, it’s about what can be done in a statically typed language, with the exception of preventing null values.
Also, I have found it’s better to compare characters than LOC (because a line can be arbitrarily long):
890 duck.rb
1788 duck.ml
Your post contains some unescaped left angle brackets (less-than symbols) like “”.
Wow, it just stripped out characters from my post, instead of escaping them. I’m now a bit displeased to be using WordPress myself.
@Justin
You’re a golf fan, I take it?
If the LOC have to be in there for syntactic or readability reasons (both, in the case of Ruby), doesn’t that count for something when it comes to calculating “succinctness”?
Robert:
I have waited a few hours to see if I understand why you quoted me, and so far enlightenment has passed me by. I said “languages—like Ruby—that include dangerous features give the fringe a broader latitude to invent new things.” And you said OCaml has syntax pre-processing, a/k/a macros.
And? Did I suggest that Symbol#to_proc is superior to macros? Or that no other languages can evolve from the fringe? Or that there are no safe features for syntactic experimentation?
I’m pleased you are discussing type-safe syntactic meta-programming. It leads very nicely into the whole discussion of why one might actually care about compilers when writing software on a day-to-day basis. But this still leaves me confused: what is it you think I was saying that is somehow in contrast to what you are saying here?
p.s. “Dangerous” is in the eye of the beholder: I suggest to you that many people consider macros and other forms of code that writes code to be dangerous, whether it is type-safe or not. Call me paranoid…
@Reg
I guess I don’t consider pre-processing macros to be “dangerous”, and therefore I don’t see Ocaml supporting many “dangerous” features, yet there’s lots of experimentation with the language (both syntax and otherwise). So Ocaml seemed to be a counter-example to the assertion that you needed dangerous features for a language to be evolving and responsive to the community.
If you’re defining pre-processing macros to be as dangerous as duck punching, I guess we’ve got a pretty wildly different definition of “dangerous”. I figure that if I can only screw up my code, it’s not dangerous — if I can screw up my code and break things that otherwise were working in libraries, that’s dangerous. To that extent, I consider some approaches to AOP in Java (the bytecode manipulating ones) to be dangerous, too.
Robert:
I personally consider macros WAY less dangerous than opening classes in a global context. I also consider Scala’s extension methods way less dangerous. And so forth.
So if what you are saying is, “there are languages that permit non-dangerous ways to experiment with syntax,” sure I personally agree with you, and would have agreed with you when I wrote my first macros in Scheme.
Now let’s play semantics: yes, I absolutely claim that Experimentation (“X”) is a good thing. I claim that certain unsafe features (“U”) lead to X in Ruby. You claim that certain safe features (“S”) lead to X in Ocaml.
So obviously, (X if U in Ruby) and (X if S in Ocaml) can both be true, as long as neither of us is claiming X iff U in Ruby or X iff S in Ocaml.
I don’t know if you are making the claim that only one of these two things can be true, or if what you are doing is demonstrating that (X iff U in Ruby) is not true by showing (X if S in Ocaml). But if you have any doubt in the matter, all I am saying is (X if U in Ruby).
Okay, so I misread your statement. I thought you were asserting that the dangerous language features (which I interpreted you to mean as a particular subset of the dynamic language features) provided capabilities to the Ruby community for language exploration and development, and that mode of language exploration and emergent syntax was somehow unique to Ruby’s family tree of languages.
But that’s not right, so I apologize for presenting you that way. I’ll go hunting for a better cite — someone who does assert something like that. I know they’re out there — or maybe that’s the kind of nonsense that only gets spouted at IT happy hours.
Since we’re playing semantics, I’ll clarify a bit what I’m saying in my piece — X if U in Ruby and X if S in Ocaml are equivalent statements modulo P (the pain caused by U which would not have been encountered in S).
Well, misunderstanding or not, I am pleased that you pointed out the value of syntactic meta-programming. No need to change the citation IMO, it is valualbe to point out that X if U does not exclude the possibility of X if S.
Which is really the theme of this post.
Now about macros being safe…
I totally take your point here, I really do. But I would like to see (and this may belong in another post) a refutation of those tired arguments that macros permit too much abstraction, that you can make code unreadable to people who don’t understand your macros, and so on.
That’s really just another form of the question “how much abstraction is too much abstraction,” I suppose. But long before people debated teh value of DSLs there were running flame-wars over whether macros were considered harmful or not.
Your perspective on that would be interesting.
Just to be clear, I’m not trying to start an argument over the usefulness of LOC measurements :) I liked your post and this is just a tiny footnote.
The trouble is that if you want to look at how much code it takes to perform a certain task, LOC helps very little, whereas the number of characters (NOC?) at least tells you how much typing and reading you have to do.
Also, while I completely agree that line breaks are an absolute necessity for readability, you can replace them with semicolons (in Ruby at least, I bet something similar in OCaml) and retain the same semantics, meaning both could be done in 1 line of code. Of course, no one would really want to do that or have to read it afterwards…
Concerning the DSL example… I’ve been studying this for quite a few minutes. Is this a REPL copy and paste? The “# …” is the code and “val …” is the result echoed back? If it is, the echoes really confused me and I thought, “Why does he comment each line of code so weirdly?” Showing the REPL dump is great if you’re demonstrating REPL, but I would leave it off if you’re demonstrating something else about code.
Yeah, that’s toplevel code: Ocaml actually comments using (* and *).
The nice thing about the REPL print-out is that it shows you the types that are being generated by the code, which is really the interesting part. But you’re probably right — for the uninitiated, it’s a bit confusing. I’ll clean up the post.
You can download the raw source files here.
I changed my mind: I’m leaving the types in. It really makes a big difference to the article, and I don’t want to lose that — instead, I’m just added a clarifying comment up at the very first item.
Added some coloration to the toplevel print outs. Hopefully that makes it a bit easier for people to read. Also brought back a few of the <fun>s that were being iterpreted as HTML tags.
you should check out haskell and its typesystem (if you did not yet)
it provides the features you describe for ocaml but it goes some steps farther and makes static typing a charm
its just plain coolness trust me
@duschendestroyer
You bring up an interesting point — this is explicitly not a tour of advanced static typing features in Ocaml. I’m simply not an experienced enough Ocaml developer to be able to give that tour right now: I’m still learning incredible static typing stunts day in and day out. Brian may be a bit stronger candidate for that (*nudge**nudge*).
I’ve looked into Haskell. Unfortunately, it’s everything-is-lazy approach freaks me out, and I’m still not quite functional enough to get away from mutable data completely.
What features of its type system do you like so much which you don’t see represented above?
One of Haskell’s advantages is that it has a Turing-complete type system. One of Haskell’s disadvantages is that it has a Turing-complete type system.
It’s an advantage as it allows you to pull stunts like scrap your boilerplate which are much harders/impossible in Ocaml’s type system, the disadvantage is that it allows you to write “martian code”- code that is simple and obvious, if you’re a martian. Or at least martian types.
In either case, my opinion is that both Haskell’s and Ocaml’s type systems are so far in advance of those of ‘common’ languages that the differences are minor. It’s not so much Haskell vr.s Ocaml vr.s F# vr.s SML, it’s a case of Haskell + Ocaml + F# + SML vr.s everything else.
She: Any code is data. Any API is DSL. So no wonder, that data can be a code in DSL.
Hi Robert,
Structural typing is not the same as Duck typing. Take a look here: http://en.wikipedia.org/wiki/Talk:Duck_typing#DuckDecoy_Example,
And: http://en.wikipedia.org/wiki/Duck_typing#Comparison_with_structural_type_systems
- Paddy.
@Paddy3118
Granted — there is that particular case where duck typing and structural typing differ, but the structural typing is close enough to duck typing for practical purposes.
“… but the structural typing is close enough to duck typing for practical purposes”.
I guess not all programs in a dynamically typed language like Python or Ruby, will change types at runtime or conditionally access methods.
But when you do start to use the dynamism then structural typing looses out.
- Paddy.
@Paddy3118
The fact of the matter is that most duck typing is something like REXML::Text does — “
outA String, IO, or any other object supporting <<( String )” (cite). There’s a type specification there, but it’s just left up to the user to derive from the documentation.But the dynamism does bring up an interesting question — when should type mangling be done, and to what extent is that same goal achievable through code generation or other safer means (e.g. variant types)?
For instance, Ruby/Rails does a LOT of typing mangling, but it’s all done at start-up (for handwavey definitions of “start-up”). That same feat should be achievable through code generation (macros, etc.). Consider PGOcaml as an example of a macro generation tool that actually queries the database.
Actually, the distinction between what Ocaml can do with structural typing and what Ruby does with duck typing isn’t so much the difference between structural and duck typing, but the difference between static and dynamic typing.
Consider code like this, as an example (in Ocaml):
let myfunc test obj = if test then obj#foo ();obj#bar();;Now, the equivalent code in Ruby only requires obj to have a foo method is test is true- if test if false, the obj#foo() is never called, and if it doesn’t exist, no problem. Ocaml, by comparison, always requires obj to have both a foo() and a bar(), always- even if foo() isn’t going to be called.
At least, this is how I read the wiki page posted above.
Personally, this is one of the things I like about Ocaml. Everyone I know of who has been programming dynamic languages long enough has horror stories about bugs hiding out in unused branches of the code for months or years, lying in wait to spring up and crash the program in the most inconvenient time possible. I’d argue that calling myfunc with an object type that didn’t have a foo as well as a bar is a bug- even if test is known to be false.
Brian,
“I’d argue that calling myfunc with an object type that didn’t have a foo as well as a bar is a bug- even if test is known to be false.”
In Python, the example case for showing Duck typing is when you use a file-like object instead of an actual file. So if you create a class that reads zip files then it is useless work to give it dummy write methods when you cannot write to a zip-file and don’t need to in your application. In fact creating dummy write methods would be extra code that you cannot test in the application. Running code coverage would never get to 100%, you would have to adjust your coverage figures for the useless code.
- Paddy.
In Python, the example case for showing Duck typing is when you use a file-like object instead of an actual file. So if you create a class that reads zip files then it is useless work to give it dummy write methods when you cannot write to a zip-file and don’t need to in your application.
Then don’t use a “file-like object”.
Static typing is exposing a code smell. I chalk this up as a win on the static side of things.
Robert,
I don’t understand the reasoning behind your last answer.
If I use a zipreader class with read/readlines etc methods that work like the same methods in the file class, then I can pass that to *unchanged* functions written as though accepting a file and get them to read from zipfiles. this could be extended in a similar way to allow the function to read from URLs or tar files in a similar fashion.
Static typing has its place, but it certainly is not the universal panacea.
- Paddy.
Nobody’s arguing that it’s a universal panacea. For instance, I’m considering building a little Twitter CLI, because all the Twitter clients I’ve bumped into are cutesy. For this, I’m almost certainly going to use a dynamic language (probably Groovy, because XMLSlurper is nice).
My reasoning is that you have a major code smell if you’re partially extending interfaces. If I have an object which someone might confuse for a “file”, then I need to implement the “file” API in its entirity to avoid violating the Principle of Least Surprise and keep depth charges from making their way into my code. (By “depth charges”, I mean the “bugs hiding out in unused branches of the code for months or years” that Brian was talking about.)
If I intend to be regularly passing around a subset of the API — reading, not writing, in your example — then my object isn’t really one object, but two. And if you really need to, just saying “This object reads only, but will explode on attempts to write” is a tolerable code smell: this is the approach Java took with its Iterator API, for instance. Personally, I prefer to allow structural typing to give me what I need, but I’m willing to take an “optional method” or two if I really must.
However, if you’re passing the object into a method and just hoping that you don’t wander down this other branch, you’re really begging for trouble. The problem is one of code maintenance: if I change the branch test, I’ve got an implicit dependency that I need to go change the code in the branch, probably in a non-obvious way, and possibly in a way that won’t be clear until some unique context hits the app in production.
The question is all about API management, and having small, atomic APIs is better than having big APIs: any argument which starts from “I have a big API, and I want to use a subset” is flawed — you have bigger problems, and the type system not letting you shoot yourself in the face is fine by me.
Now, both duck typing and structural typing allow you to easily have one- or two- method atomic APIs — the difference is that the enforcement is at run-time with duck typing, and at compile-time with structural typing.
Paddy:
You have a fundamentally bad design. The correct answer is to refactor your code so you have two classes- a read class (with a read() method) and a write class- and if a zip file, or read-only descriptor, etc. can’t be written to, it doesn’t get a write class. And static typing gaurentees you *don’t* write to a zip file.
Or, to put it in terms of my example, the correct way to do my example, if you want to pass in objects that don’t have a foo, is to split it into two different functions, like:
let myfunc_no_foo obj = obj#bar();;
let myfunc test obj = if test then obj#foo; myfunc_no_foo obj;;
Now, myfunc_no_foo accepts objects without a foo method- but myfunc requires it. If you absolutely know that foo should never be called, call myfunc_no_foo instead of myfunc.
Hi again Robert,
My answer has already been put quite well in this email by Jesse Noller at http://64.233.183.104/search?q=cache:xA5ov55E_z0J:jessenoller.com/2007/06/01/schrodingers-type-is-a-namespace-a-box/+duck-typing+-paddy3118&hl=en&ct=clnk&cd=448&gl=uk
To use Duck typing, you need to trust your programmers to know about the objects used in a function enough to make the change. In practice it works well. Code, (and your development process), smells of a lot more rank problems before making such object substitutions becomes a problem.
There is no confusion about some object being a file. You create an object that is enough of a file to get the job done without having to change the code where it is being used, and without having to supply unnecessary methods, so taking advantage of the run time typing. Small API/Big API? – Duck Typing gives less methods to code and a whole function that stays the same. I would think that in general, the less you change, and the less you add to gain the extra functionality then the less chance of introducing errors and the less code to maintain.
- Paddy.
For the other readers, here’s the non-Google cached version:
http://jessenoller.com/2007/06/01/schrodingers-type-is-a-namespace-a-box/
@Paddy3118
You seem to be wandering off into the deep weeds.
The stuff that Noller is talking about I all agree with, and I think the structural/duck typing distinction is too subtle for his argument: the abstract arguments he’s making about duck typing is true for structural typing, too.
Both Brian and I addressing your particular case — a method which has a branch, and implementing only half the branch. Both Brian and I are saying that is a Bad Idea, and so it’s a pretty weak argument in favor of strict duck typing instead of structural typing. You gave one specific example in support of the case — a read-only subset of an object with both read/write capabilities — and we both pointed out that it’s a deeply broke design and a maintenance problem. If you clean up the design (which is simple to do, as Brian pointed out), suddenly you’ll be to a point where the structural type case and the duck typing case both work the same way.
You missed the second example mentioned above: http://en.wikipedia.org/wiki/Talk:Duck_typing#DuckDecoy_Example
The above is code for what I have been saying and may allow you to understand my point where my textual description did not. If you are never going to need something then coding it is wasteful and just adds new potential sources of errors, that are hard to find because that part is not in the spec. You are doing makework to appease the structural typing gods.
By the way, I’ve found this paper on gradual typing – which seems to allow for a mix of run-time and compile time typing that looks to close the ‘gap’: http://www.cs.colorado.edu/~siek/gradual-obj.pdf
- Paddy.
@Paddy3118
That example is just another example of your run-time-dependent execution of a method, which we have denied it as something that should actually be done in code: scroll up and read through the comments again, particularly Brian’s.
When I say this is a bad idea, it’s coming from a position of experience. I’ve done this: it’s a bad plan.
Here’s my story: I had system which read in client files, and each client file had its own unique set of processing rules. So I had this dynamic structure which would create certain methods based on a particular customer’s configuration.
The problem was when one rule ended up breaking into two — I thought a rule would apply to all date fields, but it turned out one client did different things in two of their different date fields. So I broke up the rule and attached only one part. Wrote my tests, ran it, no problem. Life was great.
And then the case hit where one rule was applied, but the other wasn’t, and the date happened to be before 1960 in one case but not in the other, and suddenly the code discovered yet another place where the rule was assumed to be one.
Boom. In production. On the weekend. In just the right way so that people didn’t even notice it had exploded until it was too late.
I got (appropriately) reamed on Monday, and it was a hit to the reputation of the whole team. It was a stupid mistake on my part, but way I had it coded up made sense at the time. It was the maintenance that got tricky.
Given that experience, I don’t consider land mines like the one you are laying out to be a great idea, no matter what the type system.
Sounds as if code coverage would have shown that not all branches in your prog. had been tested. That’s not a failure in Duck Typing.
It seems as if you had a bad time, and over-compensated.
-Paddy.
All the code had been covered — I had a test that demonstrated that this one method was accessible from the class, and a test that demonstrated that if you hit this branch in this particular case, it called this other method.
The problem was an integration problem: this particular class, used with this particular data, fed into this particular method, managed to make it down a branch which made the same faulty assumption.
You can write off my case as an outlier and an acceptable risk. But in so doing, you’re making a calculation. You’re saying that the vast practical overlap between duck and structural typing isn’t enough, and structural typing is overkill because the advantage of being able to write one part of a branched API overwhelms the safety advantages provided by structural typing. For my part, I don’t run into the situation where I’m conditionally executing API methods, so I don’t really miss it. Given that I don’t miss the only thing duck typing has to offer over structural typing, and given the safety and ease of maintenance provided by structural typing, I’ll take it, thanks.
The example given in the Duck Decoy link you posted is exactly the same idea as the example I just posted up thread- you have a case where one path through the code calls a given member function and another doesn’t. We haven’t missed your point- you’ve missed ours.
Note that if you change the single line in duckworld from:
if duck.inflight:</PRE.
to:
if duck.inflight || (moon.phase == full):
Now you have a problem- this new code may work for weeks (if you start testing when the moon is in the right phase)- and then suddenly break.
This pattern is a bad idea in a number of ways- it’ll break on reasonable maintenance changes, it violates the Liskov substitution principal, etc. It may look like this code works in a dynamic language, but whatever the language, I’d argue this pattern is broken.
Hi Brian,
Again, your saying that if you don’t cover your ode during testing then bugs will arise; or that as code becomes complicated structural typing is to be preferred. I agree with the former I can see your point about the latter; but would prefer to retain the option of adding, for example , a dummy defecate method to the DuckDecoy class within an overall Duck Typing dynamically typed framework, than to be forced into always providing it, as, in most cases, you are not going to need it!
- Paddy.
Paddy –
The code coverage metric won’t save you here. Brian’s scenario is analogous to what happened to me, and I had test saturation: no line was untouched in testing. See my post above: the problem was in integration. The situation arises when you hit a particular combination of these lines over here being executed, and then those lines getting executed over there. That combination of branches and configuration caused the problems.
At this point, if your risk/gain calculus comes out such that partial method implementation wins out over structural typing’s safety, then be my guest. And when do hit this problem — and you *will* hit this problem, if you develop using dynamic languages for any period of time — then you will at least have comfort in knowing that you intentionally made your own bed.
Robert: I think Paddy’s problem is with his risk/reward calculation. This is clear to me in his statement that “in most cases, you are not going to need it”.
Paddy- would be willing to make a bet with me if you were gaurenteed that you’d win 99% of the time, and only lose 1% of time? The punch line here is that 99% of the time you win $0.10, while the 1% of the time you lose, you lose $1,000. Does that sound like a good bet?
The point here is that it’s not just the *odds* of winning or losing, it’s the cost or benefit thereof. What’s the cost of providing the dummy defecate() function, vr.s the cost of having to debug the issue much, much later? For small code bases, yes, it’s trivial- but not for even medium sized code bases of tens of thousands of lines of code. If anything, I’m understating the pain/gain difference with my $1,000 loss vr.s $0.10 win example.
Richard, Brian,
When I put your argument into my examples it is still unconvincing. The DuckTyped program could become so large and complex that it fails because a DuckDecoy needed a defecate method? Or a zipreader class needed write methods?
Such changes should be the result of carefully thought-through additions to the spec and corresponding changes to your test-spec.
Lets see, if we were relying on Structural Typing then you would have initially put in dummy methods for all the file write functionality that was not initially needed for zipreader. If you subsequently tried using the zipreader write functionality it does not magically appear. You are calling a dummy method. If you go further and are stating that you should implement the full contract of any interface , then that is going beyond Structural Typing.
With dummy methods, Structural Typing might well cause the wrong data to be returned from and errors to be found away from the root cause; whereas in Duck Typing you would be notified of the call of a missing method from where it was called.
- Paddy.
P.S: If your initial, Structural Typing enforced, dummy methods were simply raising not-implemented exceptions, then guess what, with Duck Typing you get that free!
Paddy –
My name’s Robert, not Richard.
Brian and I suggest that it’s a bad idea to pass a duck impl into duckworld unless it *could* go down both branches, because someone might wander in and change the branch conditional. They could change that branch conditional, have 100% branch and code coverage in their unit tests (using mocking, of course), even do some regression testing, and still have an explosion in production. The problem is that changing the branch conditional doesn’t immediately imply that I have to go change your particular implementation of the “duck” variable somewhere else — worse, of course, if the branch conditional is in a library and the implementation is in another library somewhere else, or if the duck implementation is being rolled at run-time. In my case, at least, I’m suggesting it because of direct personal experience where I’ve had problems like this.
Let’s say I’m the maintainer of your Duckworld package. I decide that ducks also defecate 1% of the time, even when not in flight. No problem: I go in, and change
if duck.inflight:toif duck.inflight || 1 == random.choice(1..100). I write up a mock duck which looks like this:class MockDuck(object): def __init__(self): self.soundcnt = 0 self.movecnt = 0 self.inflight = False def soundoff(self): self.soundcnt++ def move(self): self.movecnt++ def defecate(self): self.defcnt++I run my probabilistic unit test, things look great. I check my Duck class, and things still look great. So I package up my Duckworld library and deliver it to the clamoring masses.
You pull down the new Duckworld library. You run your unit test suite with the new library and DuckDecoy, and the random picks “5″, so life is great. So you package up your newly updated DuckDecoy into your code, and you ship it off to production.
Now you start getting bugs in production where your entire program crashes at *some point* when running through production. 99% of the time, you’ll be ducky. 1% of the time, you lose the Russian Roulette. If you’re lucky, the error handling is good and the stack trace is meaningful, and you can go into the DuckWorld library and see what the change was that’s causing the problem.
If you’re not lucky, you’ll get a message saying “no such method” and no other feedback. If you’re REALLY not lucky and some kind of fancy-pants retry logic or error swallowing is kicking in, you may not even notice the error for months or longer, until you’ve long-since forgotten the half-implemented duck method and you’re just trying to figure out why 1% of your queries aren’t getting persisted to the database (or whatever).
Given these kinds of problems are annoyingly common, I assert that half-implementing a duck argument’s superficial API is ALWAYS the wrong thing to do. The maintenance problem came in as soon as you assumed that returning false to one method meant that other API methods would not be called.
Given that I consider half-implementing a superficial API is the wrong thing to do, it’s unconvincing evidence for giving up structural typing.
~~ Robert.
PS: I wouldn’t dummy up the methods with “not implemented exceptions” — I would refactor duckworld, probably by making it delegate to a polymorphic method on duck. The whole “ducks defecate when in flight, but quack when not” is a piece of logic that should be internal to duck, which is where the problem really lies.
I’m truly sorry for getting your name wrong Robert.
Although you may feel slighted it was my error, rather than anyu malice on my part.
Duckworlds full, and dynamic API is to only require defecate when in flight.You can only approximate this statically. and so have to add redundant methods, and either tests that have nothing to do with the spec to achieve coverage of unnecessary methods, or sign off holes in your coverage when you test according to the spec.Someone maintaining the code, in some ways, should be better than the original writer.
When testing code that has random state then your test harness needs to be able to force values whenever random values are needed – but this is test.
If you don’t have an initial implementation of a method that is never called in your program, but is only their to ‘make up the numbers’ then if you decide later on that the functionality is neccessary you are more likely to ensure it is well tested when you have to add it. It is now part of the spec. testing its functionality can be visibly allocated resources against the revised spec. you are now more motivated to get that code correct.
Adding extra code “because it may be used in the future”, or “because it may help a future code modifier who doesn’t know what he is doing” seems like a recipe for bad software. Its creeping featurism. And the best way to aid maintenance is to have *short*, readable code that does what the spec says and no more.
- Paddy.
@Paddy
The problem here isn’t a technical one — it’s a process one. Because you don’t know the entire ins and outs of all the code you use, you can get blindsided by changes. These changes can change the required API without changing the apparent API.
You can respond that you should intimately recall the entire codebase in and out, at which point Brian is with you — but the reality is that at least I can’t intimately recall the entire codebase for even moderate size projects (2~3 developers over 6 months). Therefore, if you go that route, you’re basically asserting that dynamic languages are inappropriate even moderate size projects for me.
You seem to be missing what we’re saying over and over again: we aren’t adding extra code because it may be used in the future. We’re declaring the “duckworld” function to be broke and fixing it. So from here on out, before you say anything about adding extra code or implementing methods that are not currently being used, just feel free to delete that and leave it out of the conversation.
Hi Robert,
I’ll paraphrase your last paragraph as “Accepting the Structural Typing view of the issue; the Structural Typing solution is clearly a better solution than Duck Typing”.
I don’t share your view.
If the DuckDecoy example were written in some language without typed variables but in which inFlight were defined as a constant False, then you could statically examine the DuckWorld and prove that if inFlight is False then no defecate method would be called. Knowing that is the case then I would rather have a tool tell me that any defecate method of DuckDecoy is unreachable and nag me until it is removed – that would be a much better design flow. compared to the Structural Typing you propose. Since I don’t have access to such formal proof engines, but do have knowledge of what I am coding, then I like a flow that allows me to pare away such useless functionality, and Duck Typing allows this.
- Paddy.
You paraphrased wrong. If I was in a purely DYNAMIC language, I would still declare the duckworld function broke and fix it. It belongs as a member method on the various duck implementations, which are then responsible for knowing what functionality they need to implement. Even simpler than your solution, actually, and nets out with less code overall and a simpler API (one method).
Nice job talking about extra code and implementing methods that are not currently being used without actually saying either of those words: made me chuckle. How would your approach handle DuckDecoy’s defecate suddenly being reachable in my scenario above? After you pull down my updated Duckworld library, and defecate is suddenly reachable…? What? Keep in mind that your unit tests pass great when they execute.
If you want some kind of functionality that will gripe at you that it’s suddenly reachable, welcome to the world of structural typing. If you DON’T want it to gripe at you that it’s suddenly reachable, then you have an explosion in production and your whole alternative recommendation is equivalent to duck typing. Assuming it’s the latter, we’re still back at the same point, where your calculus says that being able to half-implement a superficial API is more important than the otherwise free safety provided by structural typing. And you are more than welcome to go off that calculus — just remember that when (not if) your code explodes in production because your implemented API and expected API got out of sync, you choose that path for yourself, and there is another way.
Robert,
You said:
“How would your approach handle DuckDecoy’s defecate suddenly being reachable in my scenario above? After you pull down my updated Duckworld library, and defecate is suddenly reachable”
As I said, if specs change then you should not be surprised that code has to change to support it. If the spec did not change, then someone is in error.
If I asked your builders to build me a one-room hunting lodge, I would be upset when they came back asking for money and time only to find that they had added a bathroom because “every house needs a bathroom”. Its not on the plans. You’ve wasted time and money.
Unfortunately, it seems that you can run a software industry like that, and make a profit, (for the software industry) ;-)
- Paddy.
@Paddy
Okay, so you weren’t explicit in giving me an answer to my question. I’m interpreting your response as going with the “it’s okay for it to explode in production” answer, based on the argument that my scenario is bogus: you’re going to catch any potential code change like that through excellent changeset communication and analysis. Cool.
Keep that in mind when you’re using a dynamic language — since you’ve thrown out safety, you can’t trust your unit tests and compilation when upgrading the codebase. When you upgrade (pull down other coworker’s code, getting new versions of libraries, etc.), you have to additionally pour over every combination of method and arguments used in the code *by hand* to make sure you aren’t suddenly calling a method that doesn’t exist. Assuming you catch them by hand, you’ll be fine.
Robert,
Think of Dynamic languages like riding a bike.
Sure, you only have two wheels,
But what you propose is like keeping the training wheels on full time.
When some experience will show, that after training,
You can ride better without them.
- Paddy.
@Paddy
Wow. You’ve actually stopped having rational conversation all together. Did I break you?
It’s been fun chatting, but at this point I’m out.
~~ Robert.
Having worked in Ocaml for a while, I’ll wholly agree with everything you said, except … I think you glossed over the annoyance of print_string, print_int, etc. Haskell apparently has Type Classes, which enables you to
print “Abc”
or
print 10
w/out any hassle — and, in fact, print anything that’s specified as “deriving Show”
That’s nit-picking, though.
@kieran
The problem with getting into this whole space is that I would also be obligated to get into functors, which is well beyond the scope of the argument. I just wanted to address duck typing vs. static typing so that it could become part of the conversation (like over here).
I’m working on my first professional ocaml gig right now, and so there’s a few things that I’m coming to discover about it which I didn’t know when I was learning the language. Brian’s promised a “why Ocaml sucks” post sometime in the future (so that we can feign being Fair and Balanced with the best of them), so we’ll get to some of those things soon.
Addressing this particular case, though: overloaded functions is something I’m not really missing. I know that a lot of people get annoyed with having to say print_int vs. print_string or whatever, but I just don’t for some reason. And operator overloading was something that bit me pretty bad in my short stint as a C++/C# developer, mainly because those compilers would chain implicit casts together and suddenly wander over Hell and back.
Did you try wc -c (instead of wc -l) for the duck typing examples?
748 duckTyping.rb
1013 duckTyping.ml
Don’t get me wrong I’m already impressed by ocaml but your statement of ocaml version being shorter doesn’t make much sense.
Let’s go through each of your 7 steps.
1) Good Information to learn what read evaluate print loop actually meant and nice to see a statically typed language with this.
2) This is a non-point in reality for careful coders as they check for explicit 0′s NUL’s and Falses (unless they are using a language where there is no difference like C). Treating NUL, 0 and False as the same unless they really are is a horrible practice that no-one should indulge in even if the language foolishly lets you.
3) This is also a non-point for careful coders. A careful coder would never employ duck-tying on an object unless they knew it really did behave like the object the code was expecting. I never once had a problem with this, there wasn’t any need to write tests because if I wasn’t 100% certain I wouldn’t use duck-typing.If you need this security you’re doing it wrong. However it was nice to see that you can do this in a statically typed language too.
4) Completely agree, I’ve always just called them API’s.
5) More feature-abuse, lambda’s are Ruby’s real closures and you can pass as many of them around at once as you like.
6) I like OCaml’s syntax, but once again you are going on about the safety of static typing. You wouldn’t go poking around with the ports responsible for reflashing BIOS in C and so long as you follow simple rules while using dynamically typed languages then it’s really a non-issue. In fact, the only type problem I have ever, ever come up with in dynamically typed languages is trying to use reserved words as variable names when I was new to a language and passing the words around instead of the variable I wanted to. Obviously this only happens when you are a newbie to a language and are bound to make mistakes, whatever language you use.
7) This point is also about gaining a type-safety feature that just isn’t necessary unless you write sloppy code.
Your post was interesting, but you could have called it instead “Why you should use a statically typed language if you are unable to control yourself and not abuse the features of a dynamically typed one”.
I don’t understand why people find it so hard to not abuse these features, I really don’t. I don’t take shortcuts like importing things from other namespaces into mine to avoid typing a few extra letters, I always make exclusive checks (if a is nil/nul(l)/none) in flow control statements rather than ambiguous ones (if a then …), and provide an “else” clause to catch results that should never happen anyway. And I never experience this pain statically typed language enthusiasts think I must do, or have to write these tests as the scenarios they dream up cannot happen in my code.
It’s worth mentioning I grew up on statically typed languages. The only major benefits I can think of for them are:
1) When you have to write code to work with other peoples code and the organisation you are part of is run and staffed by idiots who won’t accept that sloppy code is not OK. I’ve never had a problem with this but I can imagine if you are prepared to work just anywhere it’s a big deal for you (personally I’d rather work in the fields doing labour, than work for a poorly managed IT department that also won’t let me shape policy, in fact, that only just beats out McDonalds for me).
2) The awesome code completion and refactoring you can get in statically typed languages that you just cannot (practically) have to the same level in dynamically typed ones.
All the other ones can be solved by not rm -rf’ing your codes chances at life with lameness.
@anony2
First of all, you seem to be reading this post as a static typing vs. dynamic typing argument. It’s not. It’s simply showing some of the features of static typing which aren’t usually part of the conversation around type systems.
Second, you’re basically saying that you are callous to the ways in which dynamic typing brings in accidental complexity to your system. That’s not a surprise: everyone gets comfortable with their ways of coping with life’s difficulties, and then it’s “no big deal”.
This habit includes Java people, BTW. I once showed a Java person this bit of Groovy:
That code translates (handwave) into the following Java:
We’re talking half as much code in Groovy vs. Java. That’s a huge win, and I was expecting them to be dutifully impressed. They weren’t, though, and their response kinda stunned me: “Oh, you just use the IDE to generate that. That’s not really a problem.”
What I find interesting is the “sufficiently careful coder” idea as an argument against static typing- that a sufficiently careful coder simply wouldn’t make mistakes, and thus doesn’t need any safeguards. This is quite obviously true, on the other hand I’ve yet to meet the programmer who doesn’t make mistakes. And he sure as heck doesn’t look at me in the mirror.
@ Paddy
Think of Dynamic languages like riding a bike.
Sure, you only have two wheels,
But what you propose is like keeping the training wheels on full time.
When some experience will show, that after training,
You can ride better without them.
s/Dynamic languages/Languages with pointers/
s/Dynamic languages/malloc()+free() languages/
s/Dynamic languages/Assembly language/
All of these are Real Programmer fallacy arguments.
Real Programmers don’t like it when computers solve problems for them. Where’s the thrill in that?
C++ variables are not ‘nullable’ by default. Nor are C++ references. Just wanted to clarify that, because you seemed to have unfairly lumped it in with Java/Perl/Ruby. Great post otherwise!
4 Trackbacks
[...] Once you get the gist of this rant, jump to the comments for a slightly more reasoned approach. Or my follow-up post which attempts to re-open the dialog. So, I’m trying to do a little bit of XML reading/writing. Nothing major — read in an [...]
[...] An astute reader notes that I was playing fast-and-loose with the phrase “duck typing”, and that dynamic language duck typing is not quite the same as the structural typing offered by Ocaml. The conversation proceeds to analyze the nature of this difference and the practical consequences. more here [...]
[...] bit slow because they take a lot of work to do right (where I define “right” to include 7 Useful Things and Development Acceleration), and I’ve been busy with work on one hand, moving to Durham on [...]
[...] 7 Actually Useful Things You Didn’t Know Static Typing Could Do: An Introduction for the Dynam… [...]