7 Actually Useful Things You Didn’t Know Static Typing Could Do: An Introduction for the Dynamic Language Enthusiast April 14, 2008 | 06:52 am

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.

Tags: ,

  • Brian Hurt

    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.

  • http://paddy3118.blogspot.com/ Paddy3118

    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!

  • http://www.linkedin.com/in/robertfischer Robert Fischer

    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: to if 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.

  • http://paddy3118.blogspot.com/ Paddy3118

    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.

  • http://www.linkedin.com/in/robertfischer Robert Fischer

    @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.

  • http://paddy3118.blogspot.com/ Paddy3118

    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.

  • http://www.linkedin.com/in/robertfischer Robert Fischer

    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.

  • http://paddy3118.blogspot.com/ Paddy3118

    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.

  • Pingback: Enfranchised Mind » Interesting Conversations and a New Email Discipline

  • http://www.linkedin.com/in/robertfischer Robert Fischer

    @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.

  • http://paddy3118.blogspot.com/ Paddy3118

    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.

  • http://www.linkedin.com/in/robertfischer Robert Fischer

    @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.

  • kieran hervold

    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.

  • http://www.linkedin.com/in/robertfischer Robert Fischer

    @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.

  • Pingback: Enfranchised Mind » Twitter and Blogging

  • anony

    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.

  • Pingback: The Blog’s Most Popular Posts | Enfranchised Mind

  • anony2

    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.

  • http://www.smokejumperit.com Robert Fischer

    @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:

    class Foo {
      int bar
    }

    That code translates (handwave) into the following Java:

    public class Foo {
      private int bar;
      public int getBar() { return bar; }
      public void setBar(int newBar) { bar = newBar; }
    }

    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.”

  • Brian

    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.

  • Michael B

    @ 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?

  • Alvaro

    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!

  • Qwerty

    functions are a construct and closures can be a property of a construct.

    Anonymous functions do not have to be closures and your mixing closures with lambdas makes me discount everything you wrote.

  • Ryan

    C++ is way beneath any of those. It’s an absolute joke at this point.