// specifies that any data type // that is "renderable" *must* // have a "render" function that // returns a string (possibly); // we don't care how it gets the // job done, nor any other data/ // functions interface IRenderable { fun render(): String } // given that info, we can then // write functions that work with // renderable types (even before // those types are known!) // outputs renderable types render to the screen fun speak(communicative: List) { communicative.forEach { println(it.render()) } } // examples that "implement" this interface // by "overriding" the required render function data class Puppy(val name: String): IRenderable { // puppies always say the same thing :P override fun render(): String = "Woof!" } data class Emoji(val symbol: String): IRenderable { // emoji differ a bit override fun render(): String = symbol } speak(listOf( Puppy("Toto"), Puppy("Scooby"), Puppy("Lassie"), Emoji("👋"), Emoji("🤓"), ))