Duck typing is not about convenience; it is about accepting

People said that duck typing is about trading-off safety for convenience. While, to some extends, it is true, but it is not the big idea behind the philosophy and the discipline. The big ideas are all about accepting other people, being accepted by others, and evolutions by the acceptance.

What is duck typing?

Ducky
In a few words, no type!
However, people will disagree about it.
Search engines are always there for those who seek more exact or more “correct” meaning.
But, I can give you examples of duck-typed languages. You can think about Ruby, Python, and JavaScript to get an idea.

This article will explore the possibility of “Types are actually evil!” contradicting general belief at the time of writing.

The world with no type! How will this gonna work out?

There is a programming language that I tried many times to move away from but ultimately failed.
It is an ugly language with quirks and legacy staffs, yet the community is so inclusive,
they disagree with each other yet still accepting each other, they co-existed.

It is a unique occurrence and combinations that form a programming language if you asked, it will take a lot of coincidences and great timing to create the same thing. I am very doubtful if this will happen twice.

The language is called Ruby.
Ducky

Ruby has no type, or you may call it “duck typing” depends on how you look at it.
Some people say it is beautiful.
Some say it is dreadful.

For me, the language is the epitome of acceptance. I would say it works pretty well in the real world.
But how?

This leads us to the points to be made about duck typing.

Does duck typing have interfaces?

Ever heard the idea of the implicit interface?
It is used in Golang.

In Golang, when you want to implements an interface, you just create an object with the methods that fit the bill.

For example:

type Quackable interface {
	Quack()
}

type Duck struct{}

func (d *Duck) Quack() {
	fmt.Println("Quack")
}

You don’t have to tell the compiler that Duck implements Quackable. The objects just fit the bill, and *Duck variables can be stored in variables of type Quackable without doing anything special. In the Golang case, you only have to declare the interface itself, not the implementations.

In contrast, if you wanted to do the same thing in Java, you would do

interface Quackable {
  public void quack();
}

class Duck implements Quackable {
  public void quack() {
    System.out.println("quack");
  }
}

You have to declare both the interface and the implements at the class that implements the interface. Please note the implements Quackable part.

If you think Golang is better than Java in this aspect. I would tell you that there is something even better. In Ruby, you don’t even have to declare the interface. It is in the air.

class Duck
  def quack
    puts 'quack'
  end
end

If you call the implicit interface in Golang “interface”, then no interface in Ruby can be counted as a type of implicit interface too!
No interface is the extreme version of the implicit interface.

Everything is nothing. Nothing is everything.
Not a single interface code you can see, but everything implements at least an interface.

Real world example of the extreme implicit interfaces

I figured someone gonna say I am cheating, and the claim is outrageous. Let me show you a real-world example of real software I programmed.

I was once tasked to implement an RSA signer for JWT that conforms to the FIPS 140-2 security requirements. I quickly grok Amazon KMS documentation about the asymmetric key signing service they rent out cheaply that compliant with FIPS 140-2.

I noticed that I don’t have access to the raw private key ever. The private key will be generated by Amazon and stored inside the Amazon facility without crossing over the public network, or they cannot guarantee the security claimed.

And by grokking the README of the JWT gem, I also noticed that I need a private key to sign the tokens. This is not going to work.

But looking deeper

module JWT
  module Algos
    module Rsa
      module_function

      SUPPORTED = %w[RS256 RS384 RS512].freeze

      def sign(to_sign)
        algorithm, msg, key = to_sign.values
        raise EncodeError, "The given key is a #{key.class}. It has to be an OpenSSL::PKey::RSA instance." if key.class == String
        key.sign(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), msg)
      end

      def verify(to_verify)
        SecurityUtils.verify_rsa(to_verify.algorithm, to_verify.public_key, to_verify.signing_input, to_verify.signature)
      end
    end
  end
end

I figured that the private_key could be anything that implements sign(digest, message) interface.
So I cooked my own class as the private key without having the actual data of cryptographic private keys.

class JwtKmsSigner
  def initialize(\
    kms_client:,
    kms_key_id:
  )
    @kms_client = kms_client
    @kms_key_id = kms_key_id
  end

  def sign(digest = nil, message)
    effective_digest = digest || ::OpenSSL::Digest::SHA256
    response = @kms_client.sign({
      key_id: @kms_key_id,
      message: effective_digest.digest(message),
      message_type: 'DIGEST',
      signing_algorithm: 'RSASSA_PKCS1_V1_5_SHA_256',
    })
    response.signature
  end
end
  • Does it work?
    Yes, wonderfully!
  • Does the gem author agree with me that what I supplied is a private key?
    Very doubtful, I guess they don’t even notice or intended.
    But since my class conforms to the implicit interface, the library accepts it.

The example shows not just the point I made earlier that “Everything implements some kind of interface.” and also the accepting aspect of duck-typed language.

In contrast, Golang’s implicit interface must be deliberate. It is tedious and not practical to apply interfaces everywhere. In a duck-typed language, you’ll get interfaces everywhere, for free, no extra works.

The Ruby language itself is not all; Look closely; you’ll notice that the community as a whole is as inclusive as one could hope.
My theory is that: Ruby is an accepting language, which attracts a number of accepting people. Together they form an accepting community.
The gist is not at all about convenience, even if it has contributed to language adoption.

Implicit interfaces accelerate the evolutions

In programming, “map” is a verb. It means to convert some value to another value deterministically.
If I were to write an interface for it, the interface would look like this:

interface Map<A,B> {
    A convert(B input);
}

Do you know, how does the actual Map interface in Java looks like?

interface Map<A,B> {
    void clear();
    boolean containsKey(A k);
    boolean containsValue(B v);
    Set entrySet();
    B get(A k);
    boolean isEmpty();
    Set keySet();
    B put(A k, B v);
    void putAll(Map m);
    B remove(A k);
    int size();
    Collection values();
}

Ridiculous, isn’t it?
What more ridiculous is the interface is still in use widely today, decades after the definition.

At the time of Map interface designing, interfaces are thought of as blank objects with multiple implementations.
If people emphasize “object”, it is natural that the Map was defined as it happened with Java.
The belief is mostly debunked today; no new interfaces would make the same mistakes. (hopefully)
But they cannot move away from the definition even after decades-long struggling.

Google’s Guava would just throw an exception for the method that does not make sense to implements.

The type safety turns out to be the hindrance to the evolutions.
In Ruby’s example, it only requires some time of some dude, like me, to see the unseen interface and evolve the library for my use cases.

And if mistakes were introduced, it can be corrected over time without much turmoil.

It is not going to works with just only duck typing

Wonder why TypeScript is so welcomed by the JavaScript community while in Ruby community, despite some effort, just don’t care that much about type?

The answer is here.

In JavaScript, it is so unnatural to use reflections to solve typing issues.

if (object && (typeof(object.method_v1) === 'function')) {
  object.method_v1(argument);
} else if (object && (typeof(object.method_v2) === 'function')) {
  object.method_v2(argument1, argument2);
}

No one would do it naturally. It is even considered to be a hack.

Ruby people would use it without a second thought.

if object.respond_to?(:method_v1)
  object.method_v1(argument)
elsif object.respond_to?(:method_v2)
  object.method_v2(argument1, argument2)
end

The syntax kinda looks natural and fluent.

It takes much more design and some luck to create a successful duck-typed language. The points I made here are just some tips of an iceberg.

Duck typing also needs other elements to blend to form a great language.

Duck typing is for humans

If you previously read my other posts in this blog, you would know that most of my snippets prior to this post are mostly in C language. Duck-typed languages are not one size fits all. It is mainly for humans.
Compilers need types for better optimizations.
Type declarations move some works from run time to compile time leads to faster and cheaper running environments.
There are lots of problems that cannot be solved with duck typing.

At the time of writing, humans control each other with money. In the distant past, violence and fears are doing the same job. They are not as effective as money if you asked me; look how far have we go.
Money

Money accelerating human development by granting each other freedom and acceptance, allowing everything to happen parallelly at the same time. I see the same thing in duck-typed languages, I feel that we, as human beings, would better off with duck typing than more strict typing disciplines. It does not just mean we don’t make compilers brooding over other people’s code that doesn’t strictly conform to our definitions; it also means we are accepting, working alongside, and co-exist.

I will still continue to use C language; the technologies will never reach the point where we can use duck typing everywhere. I just hope more people would see duck typing as a step forward. Having too strict type checking, contrary to what the majority thinks, is a step backward for humans, IMHO.

Looks at people, focus on them (us), not the technologies. As I once said, “People are more important than software”.

I wish we have more successful duck-typed languages.

As a bonus point

Type safety !== Better quality
I can code a fairly complex project by myself with a duck-typed language without an automated test, without bugs, without regression, and with the ability to adapt over time with a stateful database.

Several months with features constantly added, not a single bug, edge case, or corner case were found.
A multi-million dollars company attempt to accomplish the same thing (same type of project with different feature sets) with Java failed to do so.

Note for those who spotted “without automated test”:
I use some tricks that allow me to work on a certain type of project without writing a test without compromising on quality and ease of future adaptation. But that is another story.

Quality is not about typing discipline or some specific way to solve a problem. Quality can be disguised and comes in different forms.
It’s magic.