Sometimes it feels like a real burden for not much gain over just passing around the basic data (a name, an age) I wanted to deal with to start with. Clojure's proposition is that in many many cases, not getting fancy with the data or over-engineering your problem representation will lead to simpler programs that are easier to maintain, giving you an alternative route to safety and maintenance instead of type-checking.
The same way you would create a keyword in Clojure to represent the age of an entity (e.g. ':entity/age') that can be put in a map describing a person or a cat.
In both cases you minimized the interface between your modules and you have less coupling.
More generally, though, it seems like row-types might be a form of static typing that would fit Rich's preferred style of programming.
let printAge object = print object.age
And it'll just check if anything passed to printAge has a field "age".
If your function only needs to know the age, then why would it take a Person or a Cat at all, instead of just accepting an age parameter? But assuming you have a reason, who says you do need to coerce anything or add any dummy data? You don't even have to go very niche to get that functionality, eg in Typescript:
class Person {
age: number
constructor (age: number) { this.age = age }
}
class Cat {
age: number
constructor (age: number) { this.age = age }
}
const printNextAge = (thing: { age: number }) => {
console.log(thing.age + 1)
}
// These all work
printNextAge(new Person(12))
printNextAge(new Cat(23))
const someRandomObject = { age: 10, colour: 'green', weight: 'heavy' }
printNextAge(someRandomObject)
// These don't:
const lady = { name: 'carol' }
printNextAge(lady)
// error TS2345: Argument of type '{ name: string; }' is not assignable to parameter of type '{ age: number; }'.
// Property 'age' is missing in type '{ name: string; }'.
const caveman = { age: 'stone' }
printNextAge(caveman)
// error TS2345: Argument of type '{ age: string; }' is not assignable to parameter of type '{ age: number; }'.
// Types of property 'age' are incompatible.
// Type 'string' is not assignable to type 'number'.
Now, if the function takes a Person, then the reason you need to fill out the rest of the stuff is because it probably wants an entire Person, not just their age. The fact that the function can tell the compiler it needs an entire Person (and not a Cat) and have it ensure that it only gets valid Persons doesn't stop you from doing anything a non-buggy program should do, it just makes the language more expressive. Even in a wordier language with a less powerful type system like Java, which obviously isn't the gold standard for static typing (and where for some reason your function was still taking an object instead of just an age int and leaving it up to the caller to extract it), it's as simple as saying: interface Aged {
int getAge();
}
and adding 'implements Aged' to your Person and Cat classes.