Learn You a Haskell for Great Good! - Miran Lipovaca [111]
ghci> [(+1),(*100),(*5)] <*> [1,2,3]
[2,3,4,100,200,300,5,10,15]
The second way is to take the first function on the left side of <*> and apply it to the first value on the right, then take the second function from the list on the left side and apply it to the second value on the right, and so on. Ultimately, it’s kind of like zipping the two lists together.
But lists are already an instance of Applicative, so how do we also make lists an instance of Applicative in this second way? As you learned, the ZipList a type was introduced for this reason. This type has one value constructor, ZipList, which has just one field. We put the list that we’re wrapping in that field. Then ZipList is made an instance of Applicative, so that when we want to use lists as applicatives in the zipping manner, we just wrap them with the ZipList constructor. Once we’re finished, we unwrap them with getZipList:
ghci> getZipList $ ZipList [(+1),(*100),(*5)] <*> ZipList [1,2,3] $
[2,200,15]
So, what does this have to do with this newtype keyword? Well, think about how we might write the data declaration for our ZipList a type. Here’s one way:
data ZipList a = ZipList [a]
This is a type that has just one value constructor, and that value constructor has just one field that is a list of things. We might also want to use record syntax so that we automatically get a function that extracts a list from a ZipList:
data ZipList a = ZipList { getZipList :: [a] }
This looks fine and would actually work pretty well. We had two ways of making an existing type an instance of a type class, so we used the data keyword to just wrap that type into another type and made the other type an instance in the second way.
The newtype keyword in Haskell is made exactly for cases when we want to just take one type and wrap it in something to present it as another type. In the actual libraries, ZipList a is defined like this:
newtype ZipList a = ZipList { getZipList :: [a] }
Instead of the data keyword, the newtype keyword is used. Now why is that? Well for one, newtype is faster. If you use the data keyword to wrap a type, there’s some overhead to all that wrapping and unwrapping when your program is running. But if you use newtype, Haskell knows that you’re just using it to wrap an existing type into a new type (hence the name), because you want it to be the same internally but have a different type. With that in mind, Haskell can get rid of the wrapping and unwrapping once it resolves which value is of which type.
So why not just use newtype instead of data all the time? When you make a new type from an existing type by using the newtype keyword, you can have only one value constructor, and that value constructor can have only one field. But with data, you can make data types that have several value constructors, and each constructor can have zero or more fields:
data Profession = Fighter | Archer | Accountant
data Race = Human | Elf | Orc | Goblin
data PlayerCharacter = PlayerCharacter Race Profession
We can also use the deriving keyword with newtype just as we would with data. We can derive instances for Eq, Ord, Enum, Bounded, Show, and Read. If we derive the instance for a type class, the type that we’re wrapping must already be in that type class. It makes sense, because newtype just wraps an existing type. So now if we do the following, we can print and equate values of our new type:
newtype CharList = CharList { getCharList :: [Char] } deriving (Eq, Show)
Let’s give that a go:
ghci> CharList "this will be shown!"
CharList {getCharList = "this will be shown!"}
ghci> CharList "benny" == CharList "benny"
True
ghci> CharList "benny" == CharList "oisters"
False
In this particular newtype, the value constructor has the following type:
CharList :: [Char] -> CharList
It takes a [Char] value, such as "my sharona" and returns a CharList value. From the preceding examples where we used the CharList value constructor,