-
Notifications
You must be signed in to change notification settings - Fork 2
Extension system
What about now writing a function that takes a list of integers and returns the string representation of the list?
// Define a function that converts a list of integers to a string
fn list_to_str(l: [int]): str {
return switch l {
case [] => "[]"
case [x] => "[" + x + "]"
case [x, ..xs] => "[" + x + ", " + list_to_str(xs) + "]"
}
}Isn't this function cool? It's a recursive function that iterates over the list and builds the string representation of the list. But, what if we want to convert a list of strings to a single string?
// Define a function that converts a list of strings to a string
fn list_to_str(l: [str]): str {
return switch l {
case [] => "[]"
case [x] => "[" + x + "]"
case [x, ..xs] => "[" + x + ", " + list_to_str(xs) + "]"
}
}This function is almost the same as the previous one, but it takes a list of strings instead of a list of integers. This is where generics come into play. Instead of defining two functions that do almost the same thing, we can define a single function that accepts a list of any type and returns a string representation of the list.
// Define a generic function that converts a list of any type to a string
fn list_to_str<A>(l: [A]): str {
return switch l {
case [] => "[]"
case [x] => "[" + ??? + "]"
case [x, ..xs] => "[" + ??? + ", " + list_to_str(xs) + "]"
}
}But this implementation lacks something important: how do we convert a generic-typed value to a string? This can't be done without black magic. But don't worry, Plume has a special solution for these cases: the extension system.
To begin with such a system, we need to understand the primitives that enable it. An extension is always linked to an interface. To be more precise, an extension is defined by an interface.
An interface is a way to define a set of methods that a type must implement. In Plume, an interface is defined by the interface keyword followed by the name of the interface and a list of methods.
// Define an interface named `Show` that defines a single method `show`
interface<A> Show<A> {
show(x: A): str
}In this example, we have defined an interface named Show that defines a single method show. This method takes a value of type A and returns a string representation of the value.
Now, an extension would extend this interface and provide an implementation for the methods defined in the interface.
// Define an extension for the `int` type that implements the `Show` interface
extend Show<int> {
show(x: int): str => x
}While defining extensions for our new interface, we still want to define a function that takes a generic list and returns a string representation of the list.
We need to specify the generic type A and constrain it to the Show interface. This way, we can call the show method on the values of the list.
// Define a generic function that converts a list of any type to a string
fn list_to_str<A extends Show>(l: [A]): str {
return switch l {
case [] => "[]"
case [x] => "[" + x.show() + "]"
case [x, ..xs] => "[" + x.show() + ", " + list_to_str(xs) + "]"
}
}The extends keyword lets us specify which interfaces should a generic type implement. In this case, we want to ensure that the generic type A implements the Show interface.
For instance, we're able to express the length of containers generically:
fn length<T extends foldable, A>(c: T<A>): int =>
x.foldl(fn (acc, _) => acc + 1, 0)The function foldl acts like a reduce function in JavaScript. It takes a function that takes two arguments: the accumulator and the current value, and returns the new accumulator. The second argument is the initial value of the accumulator. This way, we can accumulate a 1 for every element of the container and compute the sum of these 1s simultaneously.
The extension system is a powerful feature that allows you to extend the functionality of existing types without modifying their source code. It's a way to add new methods to existing types and to define new behaviors for these types. It's a way to make your code more modular and more reusable. It's a way to make your code more expressive and more concise. It's a way to make your code more Plume.