I was very impressed with Groovy when I first used it such a long time. At that time, I was drawn to its metaprogramming capabilities. More recently, I’ve been pleased by its excellent support for DSLs (like Jenkins pipelines). Lately, it’s Groovy’s approach to types that has really caught my attention.
Discussions about static vs. dynamic typing, and strong vs. weak vs. duck typing, can get confusing. I talked about it for a reason. Groovy’s approach to type checking is quite interesting, and you can find a great explanation at this article. While Groovy has added static type checking (performed at compile time) using the @TypeChecked
and @CompileStatic
annotations, I’m not as interested in that. What I find really compelling is how its optional typing approach lets us switch between dynamic typing and duck typing when working in a “dynamic mindset.” If you specify types for variables or method/function parameters, Groovy will enforce those types at runtime. However, if you don’t specify a type, it defaults to Object
and no type checks are performed. Instead, Groovy’s invocation magic (previously callsite caching, now using invokedynamic
in modern versions) provides duck typing behavior. It tries to find a method with the matching name and invoke it – if it works, it works!
Thinking about type systems reminded me of when I discovered structural typing in TypeScript time ago, and I started wondering if Groovy supports structural typing at runtime when using type declarations. In other words, instead of verifying an object’s specific type, it would check if the “shapes” match – meaning the expected type and the actual type have the same methods. The answer, it turns out, is yes The answer is No, (sort of).
To clarify, Groovy doesn’t explicitly define structural typing. However, you can make an object instance implement an interface at runtime using the as
coercion operator.
You’ll notice two distinct objects: the source object, a DefaultGreeter
instance, doesn’t implement the interface. The other is a Greeter
instance that delegates to the coerced object.
This “as operator” (AsType
method) creating a new object that delegates calls to the original object… sounds a lot like a Proxy, doesn’t it? I did some more digging, and it is! Groovy dynamically generates a proxy class (and an instance of that proxy) for you. This proxy allows you to pass runtime type checks. It then attempts to invoke the requested method on the original object; if the method isn’t found, you get a runtime error.
|
|
As the code demonstrates, Groovy creates a TextModifier1_groovyProxy
class, but not through the typical java.lang.reflect.Proxy
mechanism, as evidenced by Proxy.isProxyClass
returning false
.
I’d like to use this opportunity to highlight another intriguing, relatively new feature related to types in dynamic languages: Python’s type hinting. type hints and mypy. You can add type declarations (type hints) to your Python code, but they don’t impact runtime behavior. The interpreter doesn’t check them, so your code remains dynamically and duck typed. Type hints primarily serve as documentation. However, you can utilize a static type checker like mypy to analyze your source code and simulate compile-time static typing. This is conceptually similar to TypeScript, except you’re not transpiling from one language to another – you’re solely performing type checking.