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 what Value 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