Lesson 2: Functional Programming Part 2
Nothing is loaded from YouTube until you press play, and then without tracking cookies.
About this video
For a better learning experience, sign up and enroll in this free course at: daml.talentlms.com/plus/catalog/courses/150
Transcript
1,790 wordsCaptions as YouTube publishes them, usually machine-generated, so names such as Daml and Canton can be misheard.
- 0:00
So far, this course covered the basic data types, functions, and conditionals: the concepts that you likely have seen in a majority of other languages. In this lesson, you will review the concepts and traits that not all languages necessarily have.
- 0:17
If you’re not experienced with type strong languages or functional programming languages, this is where things may become confusing. In Daml, data types can be categorized as either variants or records. A variant is a type with multiple constructors, where each constructor wraps a value of another type.
- 0:40
You can use the data keyword to define a new variant. For example, "data Floor A equals Floor A" where the "Ae" represents any data type. The first Floor in the expression is the type constructor. The second Floor is a data constructor that can be used to create values of the "Floor A" type.
- 1:02
Suppose we specified our Floor type to be "Floor Int". We can use the data constructor Floor to create values of the type Floor Int. For instance, Floor 0 would be a valid expression of type Floor Int, where 0 is the value encapsulated by the Floor constructor.
- 1:23
Let's take a look at another example. A variant type with two constructors. The constructor “My-Constructor 1” takes a single parameter of type Int, whereas the constructor “My-Constructor 2” takes a tuhpl with two fields as parameters.
- 1:42
You can create values with either of the constructors. The built-in data type Bool is also a variant, where True and False are data constructors with zero arguments. This means that a boolean value is either True or False and cannot be instantiated with any other value.
- 2:06
Variant types where none of the data constructors take a parameter, like the Bool type, are called enums. Another very useful built-in variant is Optional. Optional captures the concept of a box, which can be empty or contain a value of “type A”.
- 2:25
Optional is a type constructor taking a “type A” as a parameter. It produces the values defined by the data constructors None and Some. The Some data constructor takes one argument, and it expects a value of “type A” as a parameter.
- 2:43
For this reason, Optional is not an enum, unlike the Bool type. Regarding variants, here are a couple things to note. In "data Confusing equals Int", the Int is a data constructor with no arguments. It has nothing to do with the built-in Int type.
- 3:03
Also, a single constructor data type might seem ambiguous as to whether it is a record or a variant type. If you want to make the distinction explicit, write “data Foo equals Foo” with the curly braces to declare a record type with no fields, or “data Foo equals Foo” with puhrentheseez for a variant with a single constructor taking a unit argument.
- 3:31
In Daml, data constructors may take at most one argument, which means the AlternativeCoordinate data constructor in the example is not valid in Daml. The data constructor of AlternativeCoordinate takes two arguments and will throw a compile error.
- 3:48
To get around this, wrap the values in a record with specific field names by either using curly braces or a with-block. Record is similar to a tuhpl in the way that it can hold different data types, but a record has named fields.
- 4:05
When you turn a variant into a record, make sure that the name of the type constructor matches the data constructor. Variants are also called sum types because the number of possible values is the sum of the number of alternatives for each data constructor.
- 4:22
The Sum-One-Two type can have three possible values. On the other hand, records are also known as product types because the number of possible values of the type is the product of the possible values of its constituent fields. The Product-Two-Two type can have four possible values.
- 4:47
Many of the object oriented programming languages, such as Ruby and Java, adopt the concept of inheritance chain. Daml doesn’t have the concept of inheritance. Instead, Daml has the concept of type-class. A type-class is an interface with a set of functions, but not all data types have access to these functions.
- 5:11
A data type that implements the functions of a type-class can be declared to be an instance of the type-class. For example, the Text data type is an instance of Show type-class, which defines the show function. This is precisely why we can call the show function with a Text argument.
- 5:31
Text is also an instance of the Eeqyu type-class, and therefore we can call the comparison operator to compare two texts. You can also create a record, a custom data type, and make it an instance of a type-class. As a reminder, you can define a record by using the “data” keyword followed by the name of your custom data type.
- 5:56
After the equal sign, write the name of the constructor of this record, which must match the name of the data type. Then you can open up either a with-block or curly braces to specify the fields and their types. You can make your custom data type an instance of a custom type-class by creating one.
- 6:17
Creating a custom type-class is very similar to writing a function signature and definition. You can define a custom type-class by using the keyword “class”. For example, you can specify the name of the type-class “Hashable”, and the “input type parameter I”, and the “output O”.
- 6:38
In the where block, specify the name of the function that must be implemented by an instance of Hashable type-class. To make your "Personal-Record" an instance of this Hashable type-class, use the “instance” keyword, followed by the name of the type-class Hashable.
- 6:56
In the place of the “input I”, pass the name of the custom data type, which is “Personal-Record”. In the place of the “output parameter O”, pass the desired return type, “Text”. In the where block, write the function definition of “hash”.
- 7:12
You can name our parameter anything, but name it “my Record” in this example, to be semantic. When the hash function is called, it takes the name and ID fields extracted from the record instance, interpolate them, and pass the result to the built-in hashing function “shah-256”.
- 7:31
Let’s test your custom record and type-class. In the test script, create an instance of the "Personal-Record" “some-Data” with the name “Alice” and ID “one two three”. If you try to print this record, you get the error, “No instance for Show Personal-Record” which means your custom data type "Personal-Record" is not an instance of the Show type-class.
- 7:58
The Show type-class is characterized by implementation of the show function. You cannot call the show function with a custom data type like your "Personal-Record", since you never defined one. So let’s define one. Making a custom data type an instance of a built-in type-class is similar to making it an instance of a custom type-class.
- 8:22
Use the instance declaration to specify the type-class Show and the data type "Personal-Record". In the where-block, define the function “show”, which takes an instance of the type "Personal-Record". This time, instead of using a parameter “myRecord”, deconstruct the "Personal-Record" to extract the keys.
- 8:45
Finally, the values can be interpolated with the rest of the Text. The error goes away and the record is printed. Alternatively, you can use the “deriving” statement. If you use “deriving”, the compiler will insert the default implementation, which is manually written here.
- 9:04
The output of using “deriving” is identical to using the “instance” declaration. The “deriving” call allows the compiler to insert a default “instance” declaration for the data type to become an instance of the specified type-classes.
- 9:20
By passing “Show” to “deriving”, the compiler makes the "Personal-Record" type an instance of Show type-class. The deriving keyword tells the compiler to derive the implementation of the show function for "Personal-Record" from the implementation of the show function for each field within the record.
- 9:41
In the example, if one of the fields in "Personal-Record" is a function, “deriving Show” produces a compile error, since a function that maps a Text to a Text is not an instance of Show type-class. This prevents the default implementation from being injected by the compiler.
- 10:00
When this happens, remember that you can still manually define an instance declaration instead of using the “deriving” statement. Also, note that all types which you intend to use as template or choice arguments must derive at least from the Show and Eq type-classes.
- 10:20
If you test again, printing “some-Data” finally works, since now your "Personal-Record" type is an instance of Show type-class. You can also pass “some-Data” as an argument to the “hash” function, since you made "Personal-Record" implement the “hash” function to make it an instance of the Hashable type-class.
- 10:42
Now that this lesson has covered types in Daml, let's discuss type parameterization. While you often create functions to handle specific data types, what if you want a function that works with a broader range of types? This is where type variables and constraints come into play.
- 11:02
For example, the add function signature specifies that it only takes and returns Ints. On the other hand, the sort function uses a type variable “Ae” to represent any data type and requires the 'Ord' constraint, indicating that "Ae" must be an instance of the Ord type-class.
- 11:22
By employing constraints like 'Ord', you can limit the types accepted by a function. If you omit the signature for add, the compiler infers the type constraint as “Additive A” to indicate that "Ae" must be of a type that can be added.
- 11:41
Type variables allow you to define functions to take a wide range of data types, while type constraints restrict the types that can be used. By limiting the types, you gain more information and unlock greater possibilities. Without the Additive type constraint, it’s hard to know what type this "Ae" can be or should be.
- 12:03
As a result, the signature “Ae maps-to Ae maps-to A” for the add function ends up with only two implementations: returning the first argument or the second. However, by adding the Additive type constraint, you can define numerous additional function implementations.
- 12:23
This lesson focused on some Daml specific concepts that may be unfamiliar to anyone who didn't have prior experience with functional programming before starting to learn Daml. Variants and records are distinct concepts and represent different types of data structures.
- 12:40
Variants, also known as sum types, represent types with multiple constructors. Records, also known as product types, represent types that combine multiple values or fields together. You can create a record, a custom data type, and make it an instance of a built-in type-class by “deriving” or a custom type-class by using the “instance” declaration.