Interfaces
Describe what a value can do, not what it is, and write functions that accept anything that fits.
An interface lists methods. Any struct or class that has those methods, with the same parameters and return types, fits the interface, without saying so anywhere. Functions that take the interface then work with all of them.
interface Shape (
def area() number
def name() string
)
struct Square (
number side = 1
def area() number -> self.side * self.side
def name() string -> "square"
)
class Circle (
number r = 1
def area() number -> 3.14 * self.r * self.r
def name() string -> "circle"
)
def report(Shape s) string -> `${s.name()}: ${s.area()}`
log report(Square(3))
log report(Circle())Square and Circle have nothing in common except two methods, and that's all report needs. There's no implements keyword: having the methods is enough.
The compiler checks the fit#
Passing something that lacks a method, or has one with the wrong types, is a compile error:
interface Shape (
def area() number
)
struct Label (
string text = ""
)
def show(Shape s) number -> s.area()
log show(Label("hi"))Mixed collections#
An array of an interface type can hold any mix of types that fit it:
interface Greeter (
def greet() string
)
struct English (
def greet() string -> "Hello"
)
struct French (
def greet() string -> "Bonjour"
)
struct Japanese (
def greet() string -> "Konnichiwa"
)
Greeter[] greeters = [English(), French(), Japanese()]
log greeters.map(g -> g.greet()).join(" / ")Only the interface's methods#
Through an interface-typed value, you can only call the interface's methods. The struct or class may have more, but the code holding the interface has promised to need only what the interface lists. That's what lets you swap one implementation for another.
Interfaces in practice#
Interfaces shine where one part of a program shouldn't care about the details of another. A notifier that sends messages can be real in production and a fake in tests:
interface Notifier (
def send(string person, string message) boolean
)
class ConsoleNotifier (
def send(string person, string message) boolean (
log `to ${person}: ${message}`
return true
)
)
class RecordingNotifier (
string[] sent = []
def send(string person, string message) boolean (
self.sent.append(person)
return true
)
)
def remindAll(Notifier n, string[] people) int (
int ok = 0
for person in people (
if n.send(person, "Meeting at 10") (
ok++
)
)
return ok
)
log remindAll(ConsoleNotifier(), ["ada", "grace"])
RecordingNotifier fake = RecordingNotifier()
remindAll(fake, ["linus"])
log fake.sentInterfaces can also constrain generics, which is the next lesson.
A class has the methods an interface lists, plus two more. Does it fit the interface?
Describable
Make both Book and Film fit the Describable interface, so the loop prints a line for each.
Dune, a book by Frank Herbert Alien, a film from 1979