Algebraic Data Types are a fundamental part of modeling data in Purescript. In this lesson, we'll look at some common methods of defining and using them.
Let's start with a blank file module name that imports prelude and sum stuff for F. We'll just have a main do log, "hello sailor." Let's define a basic data type called just basic data, and we'll use sum constructor for it.
Let's define an instance to show basic data. We're going to have show basic data, where show is going to be given sum constructor. We'll just write sum constructor. Let's go log this out, through logShow sum constructor. You'll see that some constructor has been printed below.
This is how a data type works in PureScript, Haskell and Elm, where you have a constructor that's used to construct an instance of a given type. Now let's define a basic product data type. This is going to be a product data type that takes one arg, and that will be an int.
We can define an int and show product data. Show product data, where we're going to just pattern match on the constructor and then have the argument right next to it. One arg, space, append, show X. Then we can log that out by doing logShow, one arg, 1. You'll see that the one arg 1 has been printed below.
This is what we call prototype, because you have the constructor, and an argument is applied to it. Let's define a data, sum data. This is going to have a constructor one or a constructor two. You can write a show instance for this by doing show sum data, where we're going to have to have show, and two cases that we're going to have to handle.
We can handle one now, and then we can handle the other like this. Let's try printing these two out also. We're going to have show one, and then show two. You'll see below that we have one and two. This is what's called a sum type. Now, if we forget to handle one of these cases, the compiler will warn us.
Let's try a more normal example. Data coordinates. Coordinates might be constructed with the coordinates, and then two integers. We can write an instance for show coordinates like so -- show coordinates, where show coordinates of x and y are going to be coordinates, and then show x and space and show y.
Let's go try printing this out. logShow of coordinates (1, 2). You see we have coordinates one and two below. Let's try defining some functions. We can have a function increment X that takes coordinates and will return new coordinates.
This increment X will take some coordinates, X, Y, and just return a new one, where we do X plus 1 and then Y. Likewise, we can define an increment Y which would take coordinates and return coordinates. Then we can do increment Y of coordinates X and Y, where we return the new coordinates with X and then Y + 1.
Let's try printing these out. We'll do a logShow of increment X on some coordinates, 1 and 1, and then we'll do logShow of increment Y on coordinates 1 and 1. When we run this, we'll see that we have coordinates (2, 1) and coordinates (1, 2.) This is one small example of how you might use prototypes in an application.
Let's try another example of data: day of week type. This might be a weekday, or it might be a weekend. Again, we can write an instance: show dayOfWeek type. This will be show dayOfWeek type, where show weekday will be weekday.
In this case, we'll just use underscore for a wild card for the other case. Let's define a function of what to do. What to do will take the dayOfWeek type, and return a string. We can do what to do, and we turn case underscore of. Weekday we will work, and on a weekend we will sleep.
Let's try printing this out. We'll just do a log of whatToDo, and give it a weekday. You'll see that on a weekday, we should be working. This is an example of how you might use sum types in your application. Let's try new types with new type ID. This will give us a log of the ID string.
New types are just special cases of data types where they have a single constructor, and then they wrap a single type. In the case of new types, it can actually drive an instance of new type ID, which will give us some ways that we can actually work with new types conveniently.
Make sure that when you bring this in, that you have the correct data new type here. Because this is a new type, we can actually drag a new type instance, showID. This will be showID. What this does is it uses the show instance that's defined for string. Let's make a function, makeID, that will take a string and return a maybe of ID.
In the case this is nothing, then it will imply that we had an error. We can define makeID with an empty string, which would be nothing. This says that if you try to make an ID with an empty string, then it will be nothing.
We'll see that the compiler will complain that we're not actually handling all the cases. Let's handle the other case, where we have makeID with a string that isn't an empty string, and we'll return it as just. We'll use a wrap function with the string new type.
This lets us use wrap in order to pass it in a string to get back ID. We'll use wrap, and then we'll pass end of string. Let's try logging these out, where we're going to log the show representation of that maybe.
We're going to do show makeID with an empty string, and then also try this again, but with an actual string. Then when we run this, we'll see that we get nothing, and then just ABC. Then our show representation is just ABC, because of the direct new type instance for show.