// Alternative one: subtype polymorphism. You have to implement Format as part of your type.
interface Format { String format() }
void printAll<Thing extends Format>(things List<Thing>) { things.forEach(thing => print(thing.format())) }
// Alternative two: ad-hoc polymorphism. You can implement Format separately from the type you're implementing it for
interface Format<T> { String format(T t) }
void printAll<Thing>(things List<Thing>, Format<Thing> formatter) { things.forEach(thing => print(formatter.format(thing))) }
Doing ad-hoc polymorphism manually like this is obviously annoying as hell, it's the equivalent of doing dynamic dispatch in C by explicitly passing around vtables, but e.g. Haskell and Rust have direct support for ad-hoc polymorphism through typeclasses and traits respectively. Scala's implicits still require some amount of faffing about, but still make things much easier.The bit where HKTs come in is when you want to have your interfaces talk about generics:
// HKT goes here ----V
interface Iterable<C<_>> { forEach<T>(C<T> collection, Consumer<T> fn) }
void printAll<Thing>(things List<Thing>, Format<Thing> fmt, Iterable<List> iter) { iter.forEach(things, thing => print(fmt.format(thing))) }