Introduction to Generics
Introduction to Generics
Fundamentals of Generics
Introduction to Generic Functions
Consider the following function:
let display value = printfn "%A" value;
This function takes a value as argument and displays it. Here are examples of calling that function:
let display value = printfn "%A" value; let a = 25 display a let b = 48.07 display b let c = true display c let d = "Movie Production" display d let e = (2, 6) display e
This would produce:
25 48.07 true "Movie Production" (2, 6) Press any key to close this window . . .
The function doesn't specify the type of value with which it is dealing. This means that any value can be passed to the function. In fact, this function becomes aware of the type of its parameter only when the function is called.
A generic function is one that involves one or more parameters without specifying the type of value the parameter(s) is using but still carrying its normal operation(s). In other words, at the time the function is created, it specifies its operation or role but doesn't restrict it to one particular type. Only when the function is called is the desired type specified.
Passing Generic Arguments
We already know that you can specify the data type of a parameter by following its name with a colon and the desired type, all of that in parentheses. Here is an example:
let display (value : int) = printfn "%A" value;
You also know that you can omit the type of the parameter. As a matter of fact, the above function can be written as we saw already:
let display value = printfn "%A" value;
As a result, practically all functions in F# are generic by default. Still, to indicate that the type of a parameter is not specified, instead of adding a colon and a data type to it, use the colon but replace the data type with an apostrophe and a letter or a word. Here is an example:
let display (value : 'something) = printfn "%A" value; let a = 25 display a let b = 48.07 display b let c = true display c let d = "Movie Production" display d let e = (2, 6) display e
By tradition, a letter instead of a word is used for the generic type. It can be any letter, but by tradition, most people use the letter a or the letter T:
let display (value : 'T) = printfn "%A" value;
If the function is receiving more than one parameter, if the parameters are of the same type, you can write each in its own parentheses, its name followed by a colon and the generic letter or word preceded by '. Here are examples:
let addition (a : 'T) (b : 'T) = printfn "Numbers: %A %A" a b // Passing two integers let x = 248 let y = 75 addition x y
This would produce:
Numbers: 248 75 Press any key to close this window . . .
Of course, if the parameters are of the same type, when the function is called, you must pass the same kind of value to the parameters, otherwise you would receive an error. For example the following code produces an error:
let addition (a : 'T) (b : 'T) = printfn "Numbers: %A %A" a b // Passing a decimal and an integer let x = 248.04 let y = 75 addition x y
If the function is taking more than one parameter and the parameters are of different types, you can write each in its own parentheses, its name followed by a colon and its own generic letter or word preceded by '. Here are examples:
let addition (a : 'U) (b : 'V) = printfn "%A %A" a b // Passing two integers let x = 248 let y = 75 addition x y // Passing a string and an integer let c = "Catherine Watts, " let d = 26 addition c d // Passing a string and a Boolean value let status = "Full-Time Employee" let truth = true addition status truth // Passing a decimal and a natural number let n = 282.74 let m = 60 addition n m
This would produce:
248 75 "Catherine Watts" 26 "Full-Time Employee" true 282.74 60 Press any key to close this window . . .
Creating a Generic Function
Although most functions in F# are primarily considered generic, when creating a function, you can formally specify that it is generic. To do this, just after the name of the function, type <>. Inside those symbols, type an apostrophe and a letter or word. Here is an example:
let display<'T> value = printfn "Number: %A" value;
Calling a Generic Function
You have two options to call a generic function. You can call it just as we have done so far, simply using the name of the function. Here is an example:
let display<'T> value = printfn "Number: %A" value;
let a = 25;
display a;
This would produce:
Number: 25 Press any key to close this window . . .
Here is another run of the program:
let display<'T> value = printfn "Video Status: %A" value;
let d = "Movie Production";
display d;
This would produce:
Video Status: "Movie Production" Press any key to close this window . . .
If the function is called many times in the program, then you must include the paremeter in parentheses followed by : ' and the same letter or word applied to the generic function. Here is an example:
let display<'T> (value : 'T) = printfn "%A" value; let a = 25; display a; let b = 48.07; display b; let c = true; display c; let d = "Movie Production"; display d; let e = (2, 6); display e
If the function takes more than one parameter and you want to apply the same type to all parameters, include each parameter in its parentheses with the common type. Here are examples:
let display<'T> (a : 'T) (b : 'T) = printfn "%A" (a, b); display 2 5; display 4.12 8.06; display true false; display "Movie Production" "Release Date"; display (2, 6) (0, 5);
This would produce:
(2, 5) (4.12, 8.06) (true, false) ("Movie Production", "Release Date") ((2, 6), (0, 5)) Press any key to close this window . . .
The Data Type of a Generic Parameter
Another technique to call a generic function is to specify the data type of a generic argument. To do this, on the right side of the function, type <>. Inside the operator, enter the data type. Here is an example:
let display<'T> value = printfn "Number: %A" value;
let a = 25;
display<int> a;
Here is another run of the program:
let display<'T> value = printfn "Video Status: %A" value;
let d = "Movie Production";
display<string> d;
In the same way, whenever you call the function, on the right side of the function, you can specify the data type of its argument. Here is an example:
let display<'T> (value : 'T) = printfn "%A" value; let a = 25; // Passing an integer display<int> a; let b = 48.07; // Passing a decimal number display<float> b; let c = true; // Passing a Boolean value display<bool> c; let d = "Movie Production"; // Passing a string display<string> d; // Passing a tuple let e = (2, 6); display e
You can also ask the compiler to figure out what the actual data type of the parameter is. To do this, use the underscore in place of the parameter. Here are examples:
let display (value : 'something) = printfn "%A" value; let a = 25; display<_> a; let b = 48.07; display<_> b; let c = true; display<_> c; let d = "Movie Production"; display<_> d; let e = (2, 6); display<_> e
The Data Types of Generic Parameters
If the function is taking more than one generic argument, when calling the function, if you want to specify the data type of each generic argument, on the right side of the name of the function, type <>. Inside the operator, enter each data type and separate them with commas. You can omit the generic letters or words on the right side of the name of the function when defining it. Here is an example:
let addition (a : 'U) (b : 'V) = printfn "%A %A" a b; let x = 248; let y = 75; // Passing two integers addition<int, int> x y; let c = "Catherine Watts"; let d = 26; // Passing a string and an integer addition<string, int> c d let status = "Full-Time Employee" let truth = true // Passing a string and a Boolean value addition<string, bool> status truth let n = 282.74; let m = 60; // Passing a decimal and a natural number addition<float, int> n m;
Or you can specify the generic letters or words on the right side of the name of the function when defining it. Here is an example:
let addition<'U, 'V> (a : 'U) (b : 'V) = printfn "%A %A" a b; let x = 248; let y = 75; // Passing two integers addition<int, int> x y; let c = "Catherine Watts"; let d = 26; // Passing a string and an integer addition<string, int> c d let status = "Full-Time Employee" let truth = true // Passing a string and a Boolean value addition<string, bool> status truth let n = 282.74; let m = 60; // Passing a decimal and a natural number addition<float, int> n m;
You can also ask the compiler to figure out type one, a few, or all of the parameters is (are). To do this, when calling the function, use the underscore in the placeholder of the parameter. Here are examples:
let addition<'U, 'V> (a : 'U) (b : 'V) = printfn "%A %A" a b; let x = 248; let y = 75; // Passing an unknown type and an integer addition<_, int> x y; let c = "Catherine Watts"; let d = 26; // Passing a string and an unknown type addition<string, _> c d let status = "Full-Time Employee" let truth = true // Passing two specified types addition<_, _> status truth let n = 282.74; let m = 60; // Passing a decimal and a natural number addition<float, int> n m;
Using Generic and Non-Generic Types in a Function
You can create a function that uses (a) specific data type(s) for one or more parameters and one or more generic parameters. Here is an example:
let display<'T> a (b : 'T) = printfn "%s: %A" a b display "Number" 5 display "Distance" 8.06 display "Timie Sheet Submitted" true display "Video Status" "Movie Production"
In fact, you can indicate (a) specific type for the parameter(s) whose type(s) is(are) known. Here is an example:
let display<'T> (a : string) (b : 'T) = printfn "%s: %A" a b display "Number" 5 display "Distance" 8.06 display "Timie Sheet Submitted" true display "Video Status" "Movie Production"
This would produce:
Number: 5 Distance: 8.06 Timie Sheet Submitted: true Video Status: "Movie Production" Press any key to close this window . . .
Using Various Generic Types in a Function
You can create a generic function that takes different types of parameters but whose actual types are not known at the time the function is created. To specify the generic types, after the name of the function and inside <>, type a letter or word for each type. The letter or word must be preceded by '. The types are separated by commas. Here is an example:
let addition<'U, 'V> a b =
. . .
You can access each parameter in the body of the function. If you are calling the printf() or the printfn() function, you can use %A. Here is an example:
let addition<'U, 'V> a b =
printfn "Numbers: %A %A" a b;
let x = 248;
let y = 75;
addition x y;
This would produce:
Numbers: 248 75 Press any key to close this window . . .
If the function is called many times in the program, you must include each parameter in its own parentheses followed by : and its generic type. Here is an example:
let addition<'U, 'V> (a : 'U) (b : 'V) = printfn "Numbers: %A %A" a b; // Passing two integers let x = 248; let y = 75; addition x y; // Passing a string and an integer let c = "Catherine Watts, "; let d = 26; addition c d // Passing a string and a Boolean value let status = "Full-Time Employee" let truth = true addition status truth // Passing a decimal and a natural number let n = 282.74; let m = 60; addition n m;
This would produce:
Numbers: 248 75 Numbers: "Catherine Watts, " 26 Numbers: "Full-Time Employee" true Numbers: 282.74 60 Press any key to close this window . . .
Generic Classes
Introduction
Consider the following class:
type BillCollector(item) = member this.GottenBack = item
The constructor of this class takes one parameter and assigns it to a member variable in the body of the class. Neither the constructor nor the member variable specifies the type of value the parameter will carry or use. In fact, here is a way to run the program:
type BillCollector(item) = member this.GottenBack = item let coll = BillCollector(2250.75) printfn "Money Collected: %.2F" coll.GottenBack
This would produce:
Money Collected: 2250.75
Here is another run of the program:
type BillCollector(item) = member this.GottenBack = item let coll1 = BillCollector("Honda Accord 2002") printfn "Vehicle Collected: %s" coll1.GottenBack
This would produce:
Vehicle Collected: Honda Accord 2002
When creating a class, you may want to have one or more members whose type(s) is(are) not known at the time the class is created. This is typical for a class that would be used to create a list. That is, you may want to create a list-based class (also called a collection class) that can be used to create lists of any kinds.
A generic class is one whose main member variable(s) is(are) not known in advance.
Creating a Generic Class
The above class indicates, as we saw with functions, that any class in F# is generic by default. Still, you can formally create a generic class when necessary.
To create a generic class, after the name of the class, type <>. Inside the operator, type a letter or word preceded by '. This would represent the generic type, indicating that the data type that will be used is not determined at this time. In the parentheses of any constructor that uses a parameter that will use the generic type, follow the name of the parameter with ' and the generic letter or word. In the body of the class, use the parameter as you see fit. Here is an example:
type BillCollector<'T>(item : 'T) = member self.GottenBack = item
When creating an object, you can call the constructor of the class by simply using its name. Here is an example:
let coll = BillCollector(2250.75) printfn "Money Collected: %.2F" coll.GottenBack
As an alternative, when declaring a variable from the class, to indicate that the constructor is using a generic parameter, on the right side of the name of the class, type <> and include the appropriate data type used by the parameter. Here is an example:
type BillCollector<'T>(item : 'T) = member this.GottenBack = item let coll = BillCollector<float>(2250.75) printfn "Money Collected: %.2F" coll.GottenBack
Here is another version of creating an object from the class:
type BillCollector<'T>(item : 'T) = class member this.GottenBack = item end let coll = BillCollector<string>("Honda Accord 2002"); printfn "Vehicle Collected: %s" coll.GottenBack;
A generic class can have various members that use the same type. In this case, in the parentheses of the constructor, enter each paremeter followed by : ' and the generic letter or word. Eventually, in the body of the class, use each parameter as you see fit.
type BillCollector<'T>(info : 'T, add : 'T) = class member this.GottenBack = info member this.Additional = add end let coll = BillCollector<string>("Honda Accord 2002", "Pass Due"); printfn "Vehicle Delinquency: %s" coll.GottenBack; printfn "Payment Status: %s" coll.Additional;
This would produce:
Vehicle Delinquency: Honda Accord 2002 Payment Status: Pass Due Press any key to close this window . . .
Here is another run of the program:
type BillCollector<'T>(info : 'T, add : 'T) = class member this.GottenBack = info member this.Additional = add end let coll = BillCollector<bool>(false, true); printfn "Has been keeping payments: %b" coll.GottenBack; printfn "Time to collect: %b" coll.Additional;
This would produce:
Has been keeping payment: false Time to collect: true Press any key to close this window . . .
A Generic Class With Various Types
A generic class can use different generic types. To specify those types, In the <> operator, type a lette or word preceded by ' for each paramater. In the parentheses of the constructor, apply the appropriate letter or word to the desired parameter. In the body of the class, use each parameter as you judge necessary. Here is an example:
type BillCollector<'U, 'V>(item : 'U, add : 'V) = class member this.GottenBack = item member this.Additional = add end
When creating an object from the class, on the right side of the constructor, add the <> operator. Inside that operator, enter the actual data types that the arguments use, separated by commas. In the parentheses of the constructor, pass the arguments, each conform to the corresponding type of the <> operator. Here is an example:
type BillCollector<'U, 'V>(item : 'U, add : 'V) = class member this.GottenBack = item member this.Additional = add end let coll = BillCollector<string, float>("Honda Accord 2002", 6250.85); printfn "Vehicle Collected: %s" coll.GottenBack; printfn "Item Value: %.02F" coll.Additional;
This would produce:
Vehicle Collected: Honda Accord 2002 Item Value: 6250.85
Here is another run of the program:
let coll = BillCollector<int, bool>(6, true); printfn "Delinquency Period: %d months" coll.GottenBack; printfn "Patience Run Out: %b" coll.Additional;
This would produce:
Delinquency Period: 6 months Patience Run Out: true Press any key to close this window . . .
In the same way, when creating a class, you can use as many generic types as you want in your class. You can also use a mixture of generic and non-generic parameters. In this case, create the generic types in the <> operator. In the parentheses of the class, include as many parameters as you want. Each generic parameter must be accompanied by its generic type. In the body of the class, use the parameters as you judge necessary. Here is an example:
type BillCollector<'U, 'V>(cat : 'U, item : string, add : 'V) = class member this.Category = cat member this.GottenBack = item member this.Additional = add end
When creating the object, after starting to declare the variable, on the right side of the constructor, add <> in which you must specify (only) the actual type(s) of the generic parameter(s). Here are examples:
type BillCollector<'U, 'V>(cat : 'U, item : string, add : 'V) = class member this.Category = cat member this.GottenBack = item member this.Additional = add end let coll = BillCollector<int, bool>(1, "Honda Accord 2002", true); printfn "Item Category: %d" coll.Category; printfn "Vehicle Collected: %s" coll.GottenBack; printfn "Collection Started: %b" coll.Additional;
This would produce:
Item Category: 1 Vehicle Collected: Honda Accord 2002 Collection Started: true Press any key to close this window . . .
Here is another run of the program:
type BillCollector<'U, 'V>(cat : 'U, item : string, add : 'V) = class member this.Category = cat member this.GottenBack = item member this.Additional = add end let coll = BillCollector<string, float>("Unresponsive", "Boat - Yamaha 212SS", 22480.00); printfn "Item Category: %s" coll.Category; printfn "Item to Collect: %s" coll.GottenBack; printfn "Remaining Value: %.02F" coll.Additional;
This would produce:
Item Category: Unresponsive Item to Collect: Boat - Yamaha 212SS Remaining Value: 22480.00 Press any key to close this window . . .
Generic Methods
A method is referred to as generic if it takes a generic parameter. Of course, the generic type must have been defined in the name of the class. On the right side of the name of the method, apply the same generic type. Here is an example:
type BillCollector<'T>(item : 'T) = class member this.GottenBack = item member this.Show<'T> (i : 'T) = printfn "%A" i; end
When calling the method, pass the same type of argument you pass to the constructor when creating an object. Here is an example:
type BillCollector<'T>(item : 'T) =
class
member this.GottenBack = item
member this.Show<'T> (i : 'T) =
printfn "%A" i;
end
let coll = BillCollector<string>("Honda Accord 2002");
printfn "Vehicle Collected: %s" coll.GottenBack;
coll.Show "Reason: Payment Delinquency";
This would produce:
Vehicle Collected: Honda Accord 2002 "Reason: Payment Delinquency" Press any key to close this window . . .
Constraints on Generics
Introduction
In all places where we have used generic types so far, almost any value could be used. In some cases when creating a generic function or a generic class, you may want to put a restriction on the types of values that can be used on the generic parameter. Putting a restriction on a generic parameter is referred to as constraining the generic type. Generic constraining is done using a keyword named when. You have various options.
Constraining a Generic Function or a Generic Class to a Structure
To indicate that a parameter type of a function or class must be based on an object created from a structure, in the <> operator of the function name or of the class name, after the generic letter or word, type when followed by the same generic type, followed by : struct>. Here are examples:
let display<'T when 'T : struct> value = ...; type Calculation<'T when 'T : struct>(x : 'T, y : 'T) = class end
Constraining a Generic Function or a Generic Class to a Class
To indicate that a parameter type of a function or class must be based on an object created from a class, in the <> operator, use not struct. Here are examples:
let display<'T when 'T : not struct> value = ...; type Calculation<'T when 'T : not struct>(x : 'T, y : 'T) = class end
Constraining a Generic Function or a Generic Class to Nullity
To indicate that a parameter type can allow null values, in the <> operator, instead of struct, use null. Here are examples:
let display<'T when 'T : null> value = ...; type Calculation<'T when 'T : null>(x : 'T, y : 'T) = class end
Constraining a Generic Function or a Generic Class to Comparison Operations
To indicate that Boolean comparisons can be performed on the values of the generic type, use comparison. Here are examples:
let display<'T when 'T : comparison> value = ...; type Calculation<'T when 'T : comparison>(x : 'T, y : 'T) = class end
Constraining a Generic Function or a Generic Class to Equality Operations
To indicate that the comparison for equality can be performed on the values of the generic type, use equality. Here are examples:
let display<'T when 'T : equality> value = ...; type Calculation<'T when 'T : equality>(x : 'T, y : 'T) = class end
Constraining Generic Parameters to a Certain Class
You can specify that only values or objects of a certain class can be used as parameters for a certain function or class. Of course, you must have a class. You can use an existing class or you can first create one. Once you know the class you want to use as the basis for a generic type, to put the constraint, in the <> operator of the class name, after the generic letter or word, type when followed by the same generic type, the :> operator, and the name of the class. Here are examples:
type Numeric(a, b) = class member this.Add = a + b member this.Multiply = a * b member this.Subtract = a - b member this.Divide = if b <> 0 then a / b else 0 end type Calculation<'T when 'T :> Numeric> (u : 'T) = class member this.Show = u; end
When creating an object, specify the data type as the class you had specified as the generic constraint. Here is an example:
type Numeric(a, b) = class member this.Add = a + b member this.Multiply = a * b member this.Subtract = a - b member this.Divide = if b <> 0 then a / b else 0 end type Calculation<'T when 'T :> Numeric> (u : 'T) = class member this.Show = u; end let oper = Numeric(15, 49); let result = Calculation<Numeric>(oper);
|
|||
Previous | Copyright © 2014-2024, FunctionX | Monday 23 October 2023 | Next |
|