-
Notifications
You must be signed in to change notification settings - Fork 4
0005: Email Tutorials Haskell For Beginners ‐ Creating Custom Product Types in Haskell
- 5.1. What Is a Product Type in Haskell?
- 5.2. Declaring a Custom Product Type with
data - 5.3. Type Constructors vs Data (Value) Constructors
- 5.4. Creating Values of a Custom Product Type
- 5.5. Why These Are Called “Product” Types
- 5.6. Naming Conventions and Shared Names
- 5.7. Data Constructors as Functions in GHCi
- 5.8. Type Safety, Readability, and Remaining Limitations
- 5.9. Summary: The Pattern for Defining Product Types
In this lesson, you are introduced to product types—a way of building your own custom types by combining several existing types together. A product type can be thought of as a structured “bundle” of values, such as the day, month, and year that make up a date. Instead of using an anonymous structure like a tuple, you give this bundle a meaningful name and define clearly what components it contains. This allows you to represent domain concepts directly in your type system, which makes your code more expressive and easier to understand.
To define a product type, Haskell uses the data keyword. The example in the lesson defines a custom type for dates as follows (conceptually):
- Start with the
datakeyword to indicate you are defining a new type. - Follow it with the type name, such as
DateTC. - Write an equals sign
=. - After the equals sign, write a data constructor name, such as
DateDC. - Finally, list the types of the components that make up your product type—in this case, three
Intvalues, separated by spaces.
These three Int values represent the day, month, and year. For simplicity, the lesson uses Int here and leaves stricter, more realistic representations to be discussed later. To make the new type printable in GHCi, the declaration ends with deriving Show, which instructs the compiler to generate default code so values of this type can be converted to strings and displayed.
The example uses two different names with special suffixes to make their roles clear:
-
DateTCto represent the type constructor (to the left of the=sign). -
DateDCto represent the data constructor (to the right of the=sign).
The type constructor is what you write in type signatures to refer to the type itself, such as DateTC. The data constructor (also called a value constructor) is what you use to build actual values of that type in your code. Even though the syntax may look a bit unusual at first, the separation of type-level and value-level names is an important concept in Haskell. In this initial example, the type constructor does not take any parameters, so you can think of DateTC simply as the name of the type.
Once the product type is defined, you can create values of this type by calling the data constructor with appropriate arguments. For example, two values d2 and d3 can be defined with the type DateTC. In their type signatures, you use the type constructor name (e.g., DateTC), but when you actually construct the values, you use the data constructor (e.g., DateDC). This is why the data constructor is sometimes called a “value constructor”: it is used at the value level to build concrete instances of your type.
Each such value combines three Int values into a single, well-typed date-like structure. Conceptually, the data constructor takes three integers—representing day, month, and year—and returns a value of type DateTC.
Product types get their name from an analogy with the Cartesian product of sets. Just as the Cartesian product of three sets gives you all possible triples formed from those sets, a product type like DateTC can hold any allowed combination of its component types. In this example, a value of DateTC can contain any combination of three Int values, similar to a 3-tuple (Int, Int, Int).
However, the custom type provides more structure and clarity than a bare tuple. When you see a type like DateTC, you immediately know the intention: this triple of integers represents a date. The product type gives you the same power as a tuple for combining values, but with better readability and more meaningful type signatures.
The lesson highlights a few important naming conventions:
- Both type constructors and data constructors must begin with a capital letter.
- The
TC(type constructor) andDC(data constructor) suffixes used in the example are just teaching aids. They are not required in real code.
A more idiomatic Haskell declaration would simply be:
data Date = Date Int Int IntIn this typical form, the same name Date is used for both the type constructor and the data constructor. Haskell keeps type names and value names in separate namespaces, so this does not cause a true naming conflict. You just have to remember that the Date appearing in a type signature refers to the type, and the Date used in an expression is the data constructor. This overlap can feel confusing at first, but it becomes natural with experience.
The data constructor behaves very much like a normal function. If you ask GHCi for the type of the data constructor using :t (short for :type), you will see that it takes three Int values and returns a DateTC (or Date in the simplified version). This confirms that the constructor is effectively a function whose job is to construct values of your product type.
On the other hand, if you try to ask for the type of the type constructor itself using :t, GHCi will report an error. That is because :t is intended for values and functions, not for types of types. The only things that can appear after the double colon :: in a type signature are types themselves, so whenever you are looking at a type signature, the name you see there (such as Date) refers to the type, not the value constructor. The lesson suggests comparing the type signature for the data constructor with the error from asking for the type constructor’s type to help keep the distinction clear.
One of the biggest wins from defining your own product types is increased clarity in your type signatures. When you see a function that works with Date, you know immediately that the values are intended to represent dates. By contrast, a function that takes a triple (Int, Int, Int) does not clearly communicate what those integers stand for.
However, even with a custom type like this, using raw Int components still leaves room for invalid values. You can still construct a date whose “month” field is 31, even though there is no thirty-first month. So while the custom type improves readability and expresses intent more clearly, it does not yet enforce all the constraints needed to guarantee valid dates. The lesson notes that more advanced type techniques will be needed later to restrict allowed values more tightly.
To summarize, creating your own product type in Haskell follows a straightforward pattern:
- Use the
datakeyword. - Provide a type name (type constructor).
- Write
=followed by a data constructor (value constructor). - List the component types that this constructor takes as arguments.
- Optionally add
deriving Showso that values of your new type can be printed conveniently in GHCi.
This simple pattern allows you to move from unstructured tuples to meaningful, self-describing types that reflect the concepts in your problem domain.
- Product type — A custom type made by combining multiple component types together (like a structured bundle).
- Component (field/value) — One of the parts stored inside a product type (e.g., day, month, year).
-
Tuple — An anonymous fixed-size group of values (e.g.,
(Int, Int, Int)). -
datakeyword — Used to define a new algebraic data type in Haskell. -
Type name — The name of the type you define (e.g.,
DateTC/Date). - Type constructor (TC) — The name used at the type level (in type signatures) to refer to the type.
- Data constructor (DC) / Value constructor — The name used at the value level to build actual values of the type.
- Construct (a value) — Create a value of a type by applying a data constructor to arguments.
-
Arguments (constructor arguments) — The values you pass to a data constructor (e.g., three
Ints). -
deriving Show— Asks the compiler to automatically generate aShowinstance so values can be printed. -
Show (type class) — Provides a way to convert values into printable
Stringform. -
Type signature (
::) — A declaration that states the type of a value or function. - Namespace — A naming “space” that avoids collisions; Haskell separates type names and value names.
-
Separate namespaces (types vs values) — The reason
data Date = Date ...is allowed (same spelling for type and constructor). - Naming convention (capitalized) — Type constructors and data constructors must start with a capital letter.
-
Idiomatic Haskell — Common, natural style used by experienced Haskell programmers (e.g.,
data Date = Date ...). - GHCi — Interactive environment for running expressions and inspecting values.
-
:t/:type— GHCi command that shows the type of a value/expression (like a constructor). - Constructor-as-function — The idea that a data constructor behaves like a function (takes inputs, returns a value of the type).
- Cartesian product — Math idea behind “product” types: all combinations of components form possible values.
-
3-tuple analogy —
Datewith threeInts is structurally similar to(Int, Int, Int)but more meaningful. - Type safety — Using types to prevent certain kinds of mistakes; stronger when types encode intent/constraints.
-
Readability (self-describing types) — Custom names like
Datecommunicate meaning better than raw tuples. -
Invalid value — A value that fits the type (e.g.,
Int) but doesn’t make sense in the domain (e.g., month = 31). -
Constraint / validation limitation — A weakness of using plain
Intfields: the type doesn’t enforce real-world rules yet.
Bernard Sibanda is a global Technology Entrepreneur, Web3 and Software Consultant with a deep focus on Cardano Blockchain, Midnight and Community building.
Key Positions:
- Founder, CTO, Developer Advocate cohort #1, Fullstake Developer, Cardano Ambassador, Catalyst Project Manager, DREP-WIMS:
- Co-founder of ABL Tech and Cardano Africa Live
- EBU-certified Plutus Pioneer (Plutus/Haskell)
- Cohort #1 Plutus Pioneer Developer
- Catalyst Community Reviewer & Funded Projects Manager
-
DRep for WIMS-Cardano (ID:
drep1yguj8zu48n99pv70yl6ckzt9hdgjy8yjnlqs2uyzcpafnjgu4vkul) - Intersect Developer Advocate
- Intersect Committe Member 2025-2026
- Cardano Marketer,Promoter and blogger
- Cardano Open Source Contributor
- Cardano communities and events organizer and builder
- Cardano Ambassador for South Africa
Official links:
- Stablecoins Dex
- Coxygen Global Universities
- WIMS Cardano Global
- Cardano Africa Live
- WIMS Cardano Videos
- Cardano Smart Contract Videos
- Fullstack IT Consulting
Social links: