This practice quiz emphasizes Discriminated Unions
. They are useful for times when the data that you're representing has multiple mutually exclusive cases.
Here is some good background reading for before you do these quesitions, particularly the F# for fun and profit link.
-
Discriminated Union types
The F# language reference for discriminated unions
If you want more a more in depth discussion, see F# for fun and profit's section on discriminated unions
The tour of F# section on discriminated unions
Question 1
Create a discriminated union named Action
with two cases: Buy and Sell.
- Create a value named 'bAction' and assign
Buy
to it. - Create a value named 'sAction' and assign
Sell
to it.
answertype Action =
| Buy
| Sell
let bAction = Buy
let sAction = Sell
type Action =
| Buy
| Sell
val bAction: Action = Buy
val sAction: Action = Sell
Question 2
Create a single case discriminated union to represent a particular kind of string:
- Create a discriminated union named Ticker with a single case Ticker of string.
- Then wrap the string "ABC" in your Ticker type and assign it to a value named 'aTicker'.
- Then use pattern matching to unwrap the string in
aTicker
and assign it to a value namedaTickerString
.
Discriminated unions like this are usful if you want to make
sure that you don't accidentally mix up two strings that represent
different things.
A function that takes an input with type Ticker
will not accept any string,
it will only accept inputs that have time Ticker.
answertype Ticker = Ticker of string
let aTicker = Ticker "ABC"
let (Ticker aTickerString) = aTicker
type Ticker = | Ticker of string
val aTicker: Ticker = Ticker "ABC"
val aTickerString: string = "ABC"
Question 3
Create a single case discriminated union to represent a particular kind of float:
- Create a discriminated union named Signal with a single case Signal of float.
- Then wrap the string float
1.0
in your Signal type and assign it to a value named 'aSignal'. - Then use pattern matching to unwrap the float in
aSignal
and assign it to a value namedaSignalFloat
.
answertype Signal = Signal of float
let aSignal = Signal 1.2
let (Signal aSignalFloat) = aSignal
type Signal = | Signal of float
val aSignal: Signal = Signal 1.2
val aSignalFloat: float = 1.2
Question 4
Create a discriminated union called called Funds
with two cases: MutualFund of string and HedgeFund of string.
- Create a MutualFund case of the Fund union with the string "Fidelity Magellan". Assign it to a value named "magellan".
- Create a HedgeFund case of the Fund union with the string "Renaissance Medallion". Assign it to a value named "renaissance".
answertype Funds =
| MutualFund of string
| HedgeFund of string
let magellan = MutualFund "Fidelity Magellan"
let renaissance = HedgeFund "Renaissance Medallion"
type Funds =
| MutualFund of string
| HedgeFund of string
val magellan: Funds = MutualFund "Fidelity Magellan"
val renaissance: Funds = HedgeFund "Renaissance Medallion"
Question 5
Define two types with the same cases.
type Ambiguous1 = Up | Down
type Ambiguous2 = Up | Down
If you try to assign Ambiguous1 to values,
it can be hard for the compiler (and yourself)
to figure out which of these types you mean. If you write Up
the compiler will think that you meant to use whatever was defined
last (Ambigous2).
Use fully qualified names to show how to assign the Up
case from Ambiguous1 to
a value named ambiguous1
and the Up
case from Ambiguous2 to a value named
ambiguous2
.
answertype Ambiguous1 = Up | Down
type Ambiguous2 = Up | Down
let ambiguous1 = Ambiguous1.Up
let ambiguous2 = Ambiguous2.Up
type Ambiguous1 =
| Up
| Down
type Ambiguous2 =
| Up
| Down
val ambiguous1: Ambiguous1 = Up
val ambiguous2: Ambiguous2 = Up
Question 6
Imagine that analyst recommendations have the form
type AnalystRec = Buy | Sell | Hold
You have recommendations from two analysts
let goldmanRec = Buy
let barclaysRec = Sell
You want to act on goldman recommendations as follows:
let actionOnGoldman (x: AnalystRec) =
match x with
| Buy | Hold -> "I am buying this!"
| Sell -> "I am selling this!"
The problem is that this actionOnGoldman
function will
work for both goldmanRec
and barclaysRec
.
actionOnGoldman goldmanRec // evaluates to "I am buying this!"
actionOnGoldman barclaysRec // evaluates to "I am selling this!"
-
Create a single case union called
GoldmanRec
where the single case is GoldmanRec of AnalystRec. - Create a modified
actionOnGoldman
function calledactionOnGoldmanOnly
so that it will only work on recommendations with the typeGoldmanRec
.
If wrappedGoldmanRec
is buy GoldmanRec
, the result should be
actionOnGoldmanOnly wrappedGoldmanRec // evaluates to "I am buying this!"
actionOnGoldmanOnly barclaysRec // compiler error.
answertype AnalystRec = Buy | Sell | Hold
type GoldmanRec = GoldmanRec of AnalystRec
let goldmanRec = Buy
let barclaysRec = Sell
let actionOnGoldman (x: AnalystRec) =
match x with
| Buy | Hold -> "I am buying this!"
| Sell -> "I am selling this!"
actionOnGoldman goldmanRec // what we want.
actionOnGoldman barclaysRec // oops.
// constructing it from scratch.
let wrappedGoldmanRec = GoldmanRec Buy
// or, wrapping our previously created value
let wrappedGoldmanRec2 = GoldmanRec goldmanRec
wrappedGoldmanRec = wrappedGoldmanRec2 // true
// constructing it from scratch
let actionOnGoldmanOnly (x: GoldmanRec) =
match x with
| GoldmanRec Buy | GoldmanRec Hold -> "I am buying this!"
| GoldmanRec Sell -> "I am selling this!"
// or, unwrapping the GoldmanRec with pattern matching
// in the input definition:
let actionOnGoldmanOnly2 (GoldmanRec x) =
// Since we unwrapped the goldman recommendation,
// now it is just the inner analyst recommendation.
// We can leave off the GoldmanRec that was wrapping the
// recomendation.
match x with
| Buy | Hold -> "I am buying this!"
| Sell -> "I am selling this!"
// or, since you see above that once we unwrap the goldman rec,
// it is the same as our orignal function.
let actionOnGoldmanOnly3 (GoldmanRec x) = actionOnGoldman x
// check
actionOnGoldmanOnly wrappedGoldmanRec
actionOnGoldmanOnly2 wrappedGoldmanRec
actionOnGoldmanOnly3 wrappedGoldmanRec
// These would all give compiler errors.
// Uncomment them (delete the // at the start) to test them yourself.
//actionOnGoldmanOnly barclaysRec
//actionOnGoldmanOnly2 barclaysRec
//actionOnGoldmanOnly3 barclaysRec
type AnalystRec =
| Buy
| Sell
| Hold
type GoldmanRec = | GoldmanRec of AnalystRec
val goldmanRec: AnalystRec = Buy
val barclaysRec: AnalystRec = Sell
val actionOnGoldman: x: AnalystRec -> string
val wrappedGoldmanRec: GoldmanRec = GoldmanRec Buy
val wrappedGoldmanRec2: GoldmanRec = GoldmanRec Buy
val actionOnGoldmanOnly: x: GoldmanRec -> string
val actionOnGoldmanOnly2: GoldmanRec -> string
val actionOnGoldmanOnly3: GoldmanRec -> string
val it: string = "I am buying this!"
Question 7
Imagine that stock tips have the form
type StockTip = Buy | Sell | Hold
You have recommendations from two people
let friendRec = Buy
let professorRec = Sell
You want to actions as follows:
let actionOnFriend (x: StockTip) = x
let actionOnProfessor (x: StockTip) =
match x with
| Buy -> StockTip.Sell
| Hold -> StockTip.Sell
| Sell -> StockTip.Buy
- Create a two case union called
FriendOrFoe
where the two cases are Friend of StockTip and Professor of StockTip. - Create a function called
actionFriendOrFoe
that will properly handle tips from friends and tips from professors.
Show that friendRec
and professorRec
wrapped in the FriendOrFoe
type are handled properly by actionFriendOrFoe
.
answertype StockTip = Buy | Sell | Hold
let friendRec = Buy
let professorRec = Sell
let actionOnFriend (x: StockTip) = x
let actionOnProfessor (x: StockTip) =
match x with
| Buy -> StockTip.Sell
| Hold -> StockTip.Sell
| Sell -> StockTip.Buy
// or, since we're doing the same thing with a professor's
// Buy or Hold recommendation, this could also be written
let actionOnProfessor2 (x: StockTip) =
match x with
| Buy | Hold -> StockTip.Sell
| Sell -> StockTip.Buy
type FriendOrFoe =
| Friend of StockTip
| Professor of StockTip
let wrappedFriendRec = Friend friendRec
let wrappedProfessorRec = Professor professorRec
let actionOnFriendOrFoe (x: FriendOrFoe) =
match x with
| Friend tip -> actionOnFriend tip
| Professor tip -> actionOnProfessor tip
actionOnFriendOrFoe wrappedFriendRec // evaluates to Buy
actionOnFriendOrFoe wrappedProfessorRec // evaluates to Buy
actionOnFriendOrFoe (Professor Hold) // evaluates to Sell
type StockTip =
| Buy
| Sell
| Hold
val friendRec: StockTip = Buy
val professorRec: StockTip = Sell
val actionOnFriend: x: StockTip -> StockTip
val actionOnProfessor: x: StockTip -> StockTip
val actionOnProfessor2: x: StockTip -> StockTip
type FriendOrFoe =
| Friend of StockTip
| Professor of StockTip
val wrappedFriendRec: FriendOrFoe = Friend Buy
val wrappedProfessorRec: FriendOrFoe = Professor Sell
val actionOnFriendOrFoe: x: FriendOrFoe -> StockTip
val it: StockTip = Sell
union case Ticker.Ticker: string -> Ticker
--------------------
type Ticker = | Ticker of string
val string: value: 'T -> string
--------------------
type string = System.String
union case Signal.Signal: float -> Signal
--------------------
type Signal = | Signal of float
val float: value: 'T -> float (requires member op_Explicit)
--------------------
type float = System.Double
--------------------
type float<'Measure> = float
union case GoldmanRec.GoldmanRec: AnalystRec -> GoldmanRec
--------------------
type GoldmanRec = | GoldmanRec of AnalystRec