I mean that in situations where you have a possible type error, you can substitute compiler typechecking with your own typechecking by using something like (cond (= (type foo) bar)) or (cond (= (class foo) bar)). And you can decide how you want to handle it, instead of necessarily throwing a type error. Plus, polymorphic functions allow you to handle different types with the same function instead of having to overload it. And the test suite lets you run tests like (is (instance? Bar foo)) to help you catch type errors before you push to production.
So, what I mean is, although giving up the type-checking compiler opens up the possibility of introducing runtime type errors in production, it's far from a given that they are going to happen, because dynamic languages give you plenty of other tools to prevent them.
And Java's type system is onerous because it gives you all of the rigidity of static typing but none of the power of type inference. You really notice the difference when you switch to a language like ML or Scala that allows pattern-matching and polymorphic functions, unlike Java where you have to use overloading.
So, what I mean is, although giving up the type-checking compiler opens up the possibility of introducing runtime type errors in production, it's far from a given that they are going to happen, because dynamic languages give you plenty of other tools to prevent them.
And Java's type system is onerous because it gives you all of the rigidity of static typing but none of the power of type inference. You really notice the difference when you switch to a language like ML or Scala that allows pattern-matching and polymorphic functions, unlike Java where you have to use overloading.