Type functions and traits
In the same way functions create new values from any given input, type functions create new types from any given input type — in other words, types can be “generic”. That means we can create a Maybe
that works for any value!
To make a type function, you use the “fat arrow” (=>
), where the input types go on the left and the output type goes on the right:
My-Maybe : Value => type {
My-Some Value
My-None
}
describe-maybe-number :: (My-Maybe Number) -> Text
describe-maybe-number : maybe -> when maybe {
My-Some number -> "we have a number: _" number
My-None -> "we don't have a number"
}
describe-maybe-text :: (My-Maybe Text) -> Text
describe-maybe-text : maybe -> when maybe {
My-Some text -> "we have text: _" text
My-None -> "we don't have text"
}
show (describe-maybe-number (My-Some 42))
show (describe-maybe-text (My-Some "Hello, world!"))
we have a number: 42
we have text: Hello, world!
The arrow can also be used to make constants generic, so we only need one describe-maybe
!
My-Maybe : Value => type {
My-Some Value
My-None
}
describe-maybe :: Value => (My-Maybe Value) -> Text
describe-maybe : maybe -> when maybe {
My-Some value -> "we have a value: _" value
My-None -> "we don't have a value"
}
example:7:22: error: cannot describe a `Value` value
Hmm, we run into some trouble here. The problem is that in Wipple, not all values can be converted into text! For example, let’s define a Sport
type:
Sport : type {
name :: Text
players :: Number
}
basketball : Sport {
name : "Basketball"
players : 5
}
How should we display a My-Some basketball
? Should we show the name
first and then the number of players
, or the other way around? What if we want to display an emoji instead? Wipple doesn’t assume any particular format in which to display your custom types. What we need to do is tell Wipple how to convert Sport
into text.
If you’ve used other languages, you might be familiar with the concept of an “interface”; for example, in Java, the Comparable
interface defines a compareTo
function so you can use things like Arrays.sort
.
Wipple has something similar called traits — a trait is a container for a value that changes depending on its type. We can define a trait like so:
My-Describe : Value => trait (Value -> Text)
My-Describe
accepts a Value
type, and produces a trait that stores a function to convert the Value
into Text
. Now, we can use where
to say that describe-maybe
is only available when My-Describe
is implemented for Value
:
My-Describe : Value => trait (Value -> Text)
My-Maybe : Value => type {
My-Some Value
My-None
}
describe-maybe :: Value where (My-Describe Value) => (My-Maybe Value) -> Text
describe-maybe : maybe -> when maybe {
My-Some value -> "we have a value: _" (My-Describe value)
My-None -> "we don't have a value"
}
Because My-Describe
contains a function, we can call it, passing in our value
. Now we have a Text
value we can use to fill in the placeholder!
Under the hood, _
placeholders wrap the provided value in a call to the built-in Describe
trait automatically. Describe
is defined in the same way as our My-Describe
trait, so from now on, we’ll use Describe
instead:
My-Maybe : Value => type {
My-Some Value
My-None
}
describe-maybe :: Value where (Describe Value) => (My-Maybe Value) -> Text
describe-maybe : maybe -> when maybe {
My-Some value -> "we have a value: _" value -- equivalent to `(Describe value)`
My-None -> "we don't have a value"
}
Now, if we provide a piece of text or a number, our code works!
My-Maybe : Value => type {
My-Some Value
My-None
}
describe-maybe :: Value where (Describe Value) => (My-Maybe Value) -> Text
describe-maybe : maybe -> when maybe {
My-Some value -> "we have a value: _" value
My-None -> "we don't have a value"
}
show (describe-maybe (My-Some 42))
show (describe-maybe (My-Some "Hello, world!"))
we have a value: 42
we have a value: Hello, world!
Note: We run into trouble if we provide a
My-None
. That’s because Wipple can’t infer for us whatValue
is supposed to be. We can fix this with a type annotation:My-Maybe : Value => type { My-Some Value My-None } describe-maybe :: Value where (Describe Value) => (My-Maybe Value) -> Text describe-maybe : maybe -> when maybe { My-Some value -> "we have a value: _" value My-None -> "we don't have a value" } show (describe-maybe (My-None :: My-Maybe Number))
we don't have a value
It’s rare that you’ll have to do this, though.
So what about our Sport
type from earlier? We can implement Describe
for Sport
using instance
:
Sport : type {
name :: Text
players :: Number
}
basketball : Sport {
name : "Basketball"
players : 5
}
My-Maybe : Value => type {
My-Some Value
My-None
}
describe-maybe :: Value where (Describe Value) => (My-Maybe Value) -> Text
describe-maybe : maybe -> when maybe {
My-Some value -> "we have a value: _" value
My-None -> "we don't have a value"
}
instance (Describe Sport) : sport -> do {
{
name : name
players : players
} : sport
"_ is played with _ people on each team" name players
}
show (describe-maybe (My-Some basketball))
we have a value: Basketball is played with 5 people on each team
instance
can be used in place of a variable name — whenever Wipple sees an assignment to instance
, it registers the right-hand side with the provided trait. Then, when we use describe-maybe
, Value
becomes Sport
, and Wipple looks up the corresponding instance and makes it available inside describe-maybe
.
You can also use where
to make instances conditionally available — let’s replace describe-maybe
with a custom implementation of Describe
for our My-Maybe
type!
My-Maybe : Value => type {
My-Some Value
My-None
}
Value where (Describe Value) => instance (Describe (My-Maybe Value)) : maybe -> when maybe {
My-Some value -> "we have a value: _" value
My-None -> "we don't have a value"
}
show
actually uses Describe
, so now we can pass both Sport
and My-Maybe
values to show
, and everything just works!
My-Maybe : Value => type {
My-Some Value
My-None
}
Value where (Describe Value) => instance (Describe (My-Maybe Value)) : maybe -> when maybe {
My-Some value -> "we have a value: _" value
My-None -> "we don't have a value"
}
Sport : type {
name :: Text
players :: Number
}
instance (Describe Sport) : {
name : name
players : players
} -> "_ is played with _ people on each team" name players
basketball : Sport {
name : "Basketball"
players : 5
}
show basketball
show (My-Some "Hello, world!")
show (My-Some basketball)
Basketball is played with 5 people on each team
we have a value: Hello, world!
we have a value: Basketball is played with 5 people on each team
Now that you know how type functions and traits work, you don’t need to define your own My-Maybe
type. Just use the built-in Maybe
type, along with Some
and None
— it’s implemented in exactly the same way you saw here!
Let’s put everything together and refactor our report card program to use traits. We’ll take advantage of the Read
trait, which contains a function accepting Text
and producing a Maybe Value
. prompt
uses Read
to validate the user’s input for us, so we don’t need to use repeat
either!
Grade : type {
A
B
C
D
F
}
report-card :: Grade -> Text
report-card : grade -> when grade {
A -> "top of the class"
B -> "good work"
C -> "need to study"
D or F -> "didn't pass"
}
-- Read : Value => trait (Text -> Maybe Value)
instance (Read Grade) : text -> when text {
"A" -> Some A
"B" -> Some B
"C" -> Some C
"D" -> Some D
"F" -> Some F
_ -> None
}
grade : prompt "Enter your grade"
show (report-card grade)
Enter your grade: Z
invalid input, please try again
Enter your grade: 42
invalid input, please try again
Enter your grade: A
top of the class