Reputation: 8592
I would think this is something trivial but I have spent some time on it and still, there is no clean way of doing it:
I have a type like:
type EngineTypes =
| Petrol
| Diesel
| Kerosene
I have a bunch of functions dealing with these just fine.
I would like to make a narrower type and still use the original functions with this new type.
type ValidEngineType =
| Petrol
| Diesel
The only way I could achieve this is by:
type ValidEngineType =
| Valid of engine : EngineType
| NotValid
let validEngineCheck (engine): ValidEngineType =
match region with
| "petrol" -> Valid EngineType.Petrol
| "diesel" -> Valid EngineType.Diesel
| _ -> Invalid
Later in code:
let myEngine = "petrol"
let checkEngine:Option<EngineType> =
match (validEngineCheck myEngine) with
| Valid engine -> Some engine
| Invalid ->
Console.Error.WriteLine("Unsupported engine: {0}", myEngine);
None
if checkEngine.IsNone then
Console.Error.WriteLine("Unsupported engine type")
Environment.Exit 1
// it is now safe to call this
callingOriginalCode checkEngine.Value
Is there an easier way to manage this?
Update1:
In ADA this is called constrained subtype.
https://en.wikibooks.org/wiki/Ada_Programming/Type_System#Constrained_subtype
Upvotes: 1
Views: 152
Reputation: 2807
What you want to do is deal with a sum type in arbitrary way over subsets of value constructors.
You can explicitly model the subsets and embed them in the superset:
type ValidEngineTypes = Petrol | Diesel
type EngineType = Valid of ValidEngineTypes | Kerosene
(you can do something similar with OO interface and inheritance, but lets put that on one side).
If the combinations are largely arbitrary in inconsistent with each other you can model each type explicitly and then create the "sum" type using Choice
type Petrol = Petrol
type Diesel = Diesel
type Kerosene = Kerosene
type EngineTypes = Choice<Petrol,Diesel,Kerosene>
type ValidEngineTypes = Choice<Petrol,Diesel>
if you want to define a function on EngineTypes then
let foo (engine: EngineTypes) =
match engine with
| Choice1Of3 _petrol -> "petrol"
| Choice2Of3 _diesel -> "diesel"
| Choice3Of3 _kerosene -> "kerosene"
If you don't like using ChoiceN<a,...>
then just do it explicitly using...
type EngineTypes =
| PetrolEngine of Petrol
| DieselEngine of Diesel
| KeroseneEngine of Kerosene
let foo (engine: EngineTypes) =
match engine with
| PetrolEngine _petrol -> "petrol"
| DieselEngine _diesel -> "diesel"
| KeroseneEngine _kerosene -> "kerosene"
P.S. there are things in here that can be more succinct, or idiomatic, I'm just trying to make it as simple and explicit as I can
Upvotes: 1
Reputation: 236328
Looks like you want to partition your engines into valid engines and other engines. That is a perfect task for active patterns. If you have string input as in your code:
let (|ValidEngine|_|) (engine: string) =
match engine with // case-sensitive check here
| "petrol" -> Some EngineTypes.Petrol
| "diesel" -> Some EngineTypes.Diesel
| _ -> None
And your code becomes
let myEngine = "petrol"
match myEngine with
| ValidEngine engine -> callingOriginalCode engine // it is now safe to call this
| _ ->
Console.Error.WriteLine("Unsupported engine type")
Environment.Exit 1
As you can see active pattern not only partition engine string into valid and invalid engines, but this partition also has a payload of engine value. So your callingOriginalCode
function will be called with the original EngineTypes
parameter, but only when engine type is Diesel
or Petrol
.
Upvotes: 3