Explain algebraic data types in Haskell and illustrate their usage.
In Haskell, algebraic data types (ADTs) are a powerful feature that allows you to define your own data structures by combining existing types in a structured and expressive manner. ADTs consist of two main components: sum types and product types. Let's explore the concept of ADTs in Haskell and illustrate their usage with examples.
Sum Types:
Sum types in Haskell are created by combining multiple types using the `|` (pipe) symbol to represent alternatives. Each alternative is called a constructor and represents a possible value of the sum type. Sum types are similar to the concept of union types in other programming languages. They allow you to model choices or different cases within a data type.
Here's an example of defining a sum type in Haskell:
```
haskell`data Shape = Circle Float | Rectangle Float Float`
```
In the above example, we define a `Shape` data type that can be either a `Circle` or a `Rectangle`. The `Circle` constructor takes a single `Float` argument representing the radius, while the `Rectangle` constructor takes two `Float` arguments representing the width and height.
We can then create values of the `Shape` type using the defined constructors:
```
haskell`myShape1 :: Shape
myShape1 = Circle 5.0
myShape2 :: Shape
myShape2 = Rectangle 3.0 4.0`
```
In this case, `myShape1` represents a circle with a radius of 5.0, and `myShape2` represents a rectangle with a width of 3.0 and a height of 4.0.
Product Types:
Product types in Haskell are created by combining multiple types using the Cartesian product. Product types allow you to represent a combination of values from different types. They are similar to the concept of structs or records in other languages.
Here's an example of defining a product type in Haskell:
```
haskell`data Person = Person String Int`
```
In the above example, we define a `Person` data type that consists of a `String` representing the name and an `Int` representing the age. The two components are combined using the Cartesian product.
We can create values of the `Person` type as follows:
```
haskell`john :: Person
john = Person "John" 25
mary :: Person
mary = Person "Mary" 30`
```
In this case, `john` represents a person named "John" with an age of 25, and `mary` represents a person named "Mary" with an age of 30.
Pattern Matching and ADTs:
One of the key features of ADTs in Haskell is pattern matching. Pattern matching allows you to deconstruct and extract information from values of ADTs by matching on their constructors.
For example, let's define a function that calculates the area of a shape:
```
haskell`area :: Shape -> Float
area (Circle radius) = pi * radius * radius
area (Rectangle width height) = width * height`
```
In this case, we use pattern matching to handle both cases of the `Shape` type. If the shape is a `Circle`, we extract the radius and calculate the area using the appropriate formula. If the shape is a `Rectangle`, we extract the width and height and calculate the area accordingly.
Overall, algebraic data types in Haskell provide a powerful mechanism for defining custom data structures and modeling complex data domains. They enable expressive and type-safe programming by combining sum types and product types. Pattern matching further enhances their usability, allowing for precise handling of different cases and operations on ADT values.