2.5 Unions¶
Type Union
is used for objects that can be of any of a specific finite
set of types. Union Two versions of unions are available, one with
selectors (like records) and one without. union
2.5.1 Unions Without Selectors¶
The declaration x:Union(Integer,String,Float) states that x can have values that are integers, strings or big floats. If, for example, the Union object is an integer, the object is said to belong to the Integer branch of the Union. Note that we are being a bit careless with the language here. Technically, the type of x is always Union(Integer, String, Float). If it belongs to the Integer branch, x may be converted to an object of type Integer.
The syntax for writing a Union type without selectors is
Union(type1, type2, ..., typeN)
The types in a union without selectors must be distinct.
It is possible to create unions like Union(Integer, PositiveInteger)
but
they are difficult to work with because of the overlap in the branch
types. See below for the rules FriCAS uses for converting something into
a union object.
The case infix operator returns a Boolean and can be used to determine the branch in which an object lies.
This function displays a message stating in which branch of the Union the object (defined as x above) lies.
sayBranch(x : Union(Integer,String,Float)) : Void ==
output
x case Integer => "Integer branch"
x case String => "String branch"
"Float branch"
This tries sayBranch with an integer.
sayBranch 1
Compiling function sayBranch with type Union(Integer,String,Float)
-> Void
Integer branch
Type: Void
This tries sayBranch with a string.
sayBranch "hello"
String branch
Type: Void
This tries sayBranch with a floating-point number.
sayBranch 2.718281828
Float branch
Type: Void
There are two things of interest about this particular example to which we would like to draw your attention.
- FriCAS normally converts a result to the target value before passing it to the function. If we left the declaration information out of this function definition then the sayBranch call would have been attempted with an Integer rather than a Union, and an error would have resulted.
- The types in a Union are searched in the order given. So if the type were given as sayBranch(x: Union(String,Integer,Float,Any)): Void then the result would have been String branch because there is a conversion from Integer to String.
Sometimes Union types can have extremely long names. FriCAS therefore
abbreviates the names of unions by printing the type of the branch first
within the Union and then eliding the remaining types with an ellipsis
(...)
.
Here the Integer branch is displayed first. Use ::
to create a
Union object from an object.
78 :: Union(Integer,String)
Type: Union(Integer,...)
Here the String branch is displayed first.
s := "string" :: Union(Integer,String)
Type: Union(String,...)
Use typeOf to see the full and actual Union type. typeOf
typeOf s
Type: Domain
A common operation that returns a union is exquoexquoInteger which returns the exact quotient if the quotient is exact,
three := exquo(6,2)
Type: Union(Integer,...)
and “failed” if the quotient is not exact.
exquo(5,2)
Type: Union(“failed”,...)
A union with a “failed” is frequently used to indicate the failure or lack of applicability of an object. As another example, assign an integer a variable r declared to be a rational number.
r: FRAC INT := 3
Type: Fraction Integer
The operation retractIfCan
tries to retract the
fraction to the underlying domain Integer. It produces a union object.
Here it succeeds.
retractIfCan(r)
Type: Union(Integer,...)
Assign it a rational number.
r := 3/2
Type: Fraction Integer
Here the retraction fails.
retractIfCan(r)
Type: Union(“failed”,...)
2.5.2 Unions With Selectors¶
Like records (ugTypesRecords ), you can write Union types selector:union with selectors. union:selector
The syntax for writing a Union type with selectors is
Union(selector1:type1, selector2:type2, ..., selectorN:typeN)
You must be careful if a selector has the same name as a variable in the workspace. If this occurs, precede the selector name by a single quote quote. selector:quoting It is an error to use a selector that does not correspond to the branch of the Union in which the element actually lies.
Be sure to understand the difference between records and unions with selectors. union:difference from record Records can have more than one component and the selectors are used to refer to the components. record:difference from union Unions always have one component but the type of that one component can vary. An object of type Record(a: Integer, b: Float, c: String) contains an integer and a float and a string. An object of type Union(a: Integer, b: Float, c: String) contains an integer or a float or a string.
Here is a version of the sayBranch function (cf. ugTypesUnionsWOSel ) that works with a union with selectors. It displays a message stating in which branch of the Union the object lies.
sayBranch(x:Union(i:Integer,s:String,f:Float)):Void==
output
x case i => "Integer branch"
x case s => "String branch"
"Float branch"
Note that case uses the selector name as its right-hand argument. case If you accidentally use the branch type on the right-hand side of case, false will be returned.
Declare variable u to have a union type with selectors.
u : Union(i : Integer, s : String)
Type: Void
Give an initial value to u.
u := "good morning"
Type: Union(s: String,...)
Use case to determine in which branch of a Union an object lies.
u case i
Type: Boolean
u case s
Type: Boolean
To access the element in a particular branch, use the selector.
u.s
Type: String