I am new to F# and am trying to develop a snake game so pardon me if this sounds stupid.
For now, this is the model for the game:
// value objects
type Position = int * int
type Block = { Position: Position }
type Tail = { Blocks: Block list }
type Direction =
| North
| South
| West
| East
//
// entities
type Snake = { Tail: Tail }
type World = { Snake: Snake }
//
To make things simpler when moving the snake, I would like for each Direction to have its own Position, just like:
type Direction =
| North of Position (0, 1)
| South of Position (0, -1)
| West of Position (-1, 0)
| East of Position (0, 1)
so I can just apply it here:
let moveSnakeHead direction snake =
// easily move the snake's head
// tail[0].x += direction.x, tail[0].y += direction.y
However, it seems to me that it is not possible to do that of Position (x, y) inside the discriminated union?
Could someone explain why? I am trying my best to learn types. And what would be the alternatives?
Make sure that you are clear on the distinction between values and types in F#. This is a common pitfall for people new to F#, especially around discriminated unions.
1 is a value of type int.
(1, 1) is a value of type (int * int).
When you define a DU type, each case can hold data of a certain type:
type DUType =
| DUCase1 of int
So each DU case can contain any int, not just a particular int.
You also have a type alias in your code: type Position = int * int. This is just saying that you can write Position anywhere and it will mean the same as int * int. It's not actually another type.
So in your code you can't say that a DU case must always contain a certain value. You need to write a function instead that takes a Direction and returns a Position:
type Direction =
| North
| South
| West
| East
let directionToPostion direction : Position =
match direction with
| North -> (0, 1)
| South -> (0, -1)
| West -> (-1, 0)
| East -> (0, 1)
Any F# code you write will generally always be in 3 "modes":
Value
Type
Pattern (as in pattern matching)
Try to make sure you know which one of the three you're in at any given time.
Abusing the answer from #TheQuickFrownFox I actually got it working the way I think you want. I think your data types are overly complex, but it is possible to create a snake game like this. Note the usage of reference types and mutables.
// value objects
type Position = int * int
type Block = { mutable Position: Position }
type Tail = { Blocks: Block list }
type Direction =
| North
| South
| West
| East
//
// entities
type Snake = { Tail: Tail }
//
let directionToPostion = function
| North -> (0, 1)
| South -> (0, -1)
| West -> (-1, 0)
| East -> (0, 1)
let moveSnakeHead (direction: Direction) (snake: Snake ref) =
// easily move the snake's head
let (dirX, dirY) = directionToPostion direction
let snakeHeadPos = (!snake).Tail.Blocks.[0].Position
(!snake).Tail.Blocks.[0].Position <- (dirX + fst snakeHeadPos, dirY + snd snakeHeadPos)
let blocks: Block list = [ {Position = (5,3)}; {Position = (4,2)} ]
let tail: Tail = { Blocks = blocks }
let snake = ref <| {Tail = tail}
printfn "%A" blocks
moveSnakeHead North snake
printfn "%A" blocks
Quick note:
F# is not a clean functional language, so you can use it like an object-oriented language with a bit of work, but it is not the preferred way. Optimally you would have a function which reads the snake (I recommend simply using the type type Snake = (int * int) list, and outputs (maps) it into a new list containing the updated positions. This would be cleaner, easier to maintain, and more adherent to the design goals of F#.
Edit:
I decided to come back and update my answer to contain which I think would be the canonical way of doing this in F#. I think you will find this cleaner and easier to read:
type Snake = (int * int) list
type Direction = North | South | East | West
let moveSnake snake dir =
if List.isEmpty snake then []
else
let h = List.head snake
match dir with
| North -> (fst h, snd h - 1) :: List.tail snake
| South -> (fst h, snd h + 1) :: List.tail snake
| East -> (fst h + 1, snd h) :: List.tail snake
| West -> (fst h - 1, snd h) :: List.tail snake
let snake = [(5,3); (1,2)]
printfn "%A" snake
printfn "%A" <| moveSnake snake North
If you really want, you can declare the snake variable mutable, so that you can change the snake. But I recommend staying away from this and having your program strictly functional as far as possible.
Related
When should I use a function within a function versus a separate private function?
I observed that a function that I wrote was fairly long:
let optionsFor piece (positions:Space list) =
let yDirection = match piece with
| Black _ -> -1
| Red _ -> 1
let sourceX , sourceY =
match piece with
| Black (checker , pos) -> pos
| Red (checker , pos) -> pos
let optionsForPiece =
(fun pos -> pos = ((sourceX - 1) , (sourceY + yDirection)) ||
pos = ((sourceX + 1) , (sourceY + yDirection)))
let availableSelection =
(fun space -> match space with
| Available pos -> Some pos
| Allocated _ -> None)
let availablePositions =
positions |> List.filter toAvailable
|> List.choose availableSelection
availablePositions |> List.filter optionsForPiece
Thus, I considered refactoring the function above into several small functions.
However, I am not sure if this is necessary in functional programming.
What is the current recommendation on inner functions versus extracting them out to private functions?
Appendix:
open NUnit.Framework
open FsUnit
(* Types *)
type Black = BlackKing | BlackSoldier
type Red = RedKing | RedSoldier
type Coordinate = int * int
type Piece =
| Black of Black * Coordinate
| Red of Red * Coordinate
type Space =
| Allocated of Piece
| Available of Coordinate
type Status =
| BlacksTurn | RedsTurn
| BlackWins | RedWins
(* Functions *)
let black coordinate = Allocated (Black (BlackSoldier , coordinate))
let red coordinate = Allocated (Red (RedSoldier , coordinate))
let startGame () =
[ red (0,0); red (2,0); red (4,0); red (6,0)
red (1,1); red (3,1); red (5,1); red (7,1)
red (0,2); red (2,2); red (4,2); red (6,2)
Available (1,3); Available (3,3); Available (5,3); Available (7,3)
Available (0,4); Available (2,4); Available (4,4); Available (6,4)
black (1,5); black (3,5); black (5,5); black (7,5)
black (0,6); black (2,6); black (4,6); black (6,6)
black (1,7); black (3,7); black (5,7); black (7,7) ] , BlacksTurn
let private toAvailable =
(fun space -> match space with
| Available pos -> true
| _ -> false)
let available (positions:Space list) = positions |> List.filter toAvailable
let optionsFor piece (positions:Space list) =
let yDirection = match piece with
| Black _ -> -1
| Red _ -> 1
let sourceX , sourceY =
match piece with
| Black (checker , pos) -> pos
| Red (checker , pos) -> pos
let optionsForPiece =
(fun pos -> pos = ((sourceX - 1) , (sourceY + yDirection)) ||
pos = ((sourceX + 1) , (sourceY + yDirection)))
let availableSelection =
(fun space -> match space with
| Available pos -> Some pos
| Allocated _ -> None)
let availablePositions =
positions |> List.filter toAvailable
|> List.choose availableSelection
availablePositions |> List.filter optionsForPiece
This is more opinion-based, but I'll offer my opinion.
My rule of thumb would be that if the "helper" function is tighly associated with the "main" function, I'd write it as a nested function. If they're not tightly associated, I'd write the helper function as a separate function -- and I might not even make it private, because you never know when it might come in handy for other code in a different module.
An example of a tightly associated inner function would be the kind of loop-with-accumulator function that you often end up writing in recursive functional programming. For example, here's some code I wrote for an F# programming exercise:
module BinarySearchTree
type Node<'T> =
{ left: Node<'T> option
value: 'T
right: Node<'T> option }
let singleton v = { left = None; value = v; right = None }
let rec insert v t =
if v <= t.value
then match t.left with
| None -> { t with left = singleton v |> Some }
| Some n -> { t with left = insert v n |> Some }
else match t.right with
| None -> { t with right = singleton v |> Some }
| Some n -> { t with right = insert v n |> Some }
let fromList l =
match l with
| [] -> failwith "Can't create a tree from an empty list"
| hd::tl ->
tl |> List.fold (fun t v -> insert v t) (singleton hd)
let toList t =
let rec loop acc = function
| None -> acc
| Some node ->
(loop [] node.left) # (node.value :: (loop [] node.right))
loop [] (Some t)
Take a look at that last toList function. It has an inner function that I called loop, which would make no sense as a standalone function. It is so tightly associated to the toList function that it just makes sense to keep it as an inner function, not accessible from outside toList.
However, when I wrote the fromList function, I did not define insert inside it as an inner function. The insert function is useful on its own, quite apart from the functionality of fromList. So I wrote insert as a separate function. Even though fromList is the only function in my code that actually uses insert, that might not necessarily be true in the future. I might write a fromArray function, where I don't want to reuse fromList for efficiency's sake. (I could write fromArray as let fromArray a = a |> List.ofArray |> fromList, but that creates an unnecessary list that I'm just going to throw away when I'm done; it makes more sense, efficiency-wise, to directly iterate over the array and call insert as appropriate.)
So there's an example of when it's wise to use nested inner functions vs. separate functions in the same module. Now let's look at your code.
yDirection - This is a variable, but could be turned into a function taking piece as a parameter. As a function, it looks like it could be useful in many different functions. My judgment: separate.
sourceX and sourceY - These are variables, not functions, but you could turn that match into a function called source that returns a tuple, and then call it in your optionsFor function to set the values of sourceX and sourceY. In my opinion, that source function makes most sense as a separate function.
optionsForPiece - This function looks tightly associated with the optionsFor function, such that you probably wouldn't want to call it from elsewhere. My judgment: nested.
availableSelection - This could be quite useful in several situations, not just optionsFor. My judgment: separate.
availablePositions - This is a variable, but could easily be turned into a function that takes positions as a parameter and returns which ones are available. Again, that could be useful in several situations. My judgment: separate.
So by splitting out all the functions that seem like they could be re-used, we've gotten your optionsFor function down to the following:
// Functions yDirection, source, availableSelection,
// and availablePositions are all defined "outside"
let optionsFor piece (positions:Space list) =
let yDir = yDirection piece
let sourceX , sourceY = source piece
let optionsForPiece pos =
pos = ((sourceX - 1) , (sourceY + yDir)) ||
pos = ((sourceX + 1) , (sourceY + yDir))
positions |> availablePositions |> List.filter optionsForPiece
That's a lot more readable when you revisit the code later, plus you get the benefit of having more reusable functions (like availableSelections) around for when you write the next bit of your code.
Is it possible to create a discriminated union type via a unit of measurement tag in F#?
I want to write sth. like the following:
type DomainObject =
| Pixel of int
| ScaledPixel of int
| Centimeter of float
| Unset
let var1 = 10<px> // should equal: let var1 = Pixel(10)
let var2 = 0<us> // should equal: let var2 = Unset
let process sth =
match sth with
| Pixel(p) -> ...
| Centimeter(c) -> ...
// etc.
With NumericLiterals such things are possible. But then one can only use a small amount of Literals like Neil P. showed.
As I said in the comment, the simple answer is no.
In a way, you are trying to misuse one F# feature (units of measure) to emulate a feature that might exist in other languages (suffix operators), which is probably a bad thing to do in the first place, because (even if it was possible), the resulting code would be quite confusing.
If you simply want to reverse the order of the arguments so that the number comes before the unit name, you can use the piping operator and write:
let var1 = 10 |> Pixel
let var2 = Unset
This essentially gives you a way to write "suffix operators", but using standard F# idioms.
I don't think that this special combination is possible but you can go with smart constructors if you like:
module Domain =
[<Measure>] type px
[<Measure>] type spx
[<Measure>] type cm
// ...
type DomainObject =
| Pixel of float<px>
| ScaledPixel of float<spx>
| Centimeter of float<cm>
| Unset
let inline pixel f = Pixel <| float f * 1.0<px>
let inline scaledPixel f = ScaledPixel <| float f * 1.0<spx>
let unset = Unset
// ...
let var1 = pixel 10
let var2 = unset
let process sth =
match sth with
| Pixel(p) -> ...
| Centimeter(c) -> ...
// etc.
I think this is reasonable close - if you want you can make the constructors private and add active-patterns (to reenable pattern-matching) or accessors to fully encapsulate the implementation-details.
If you get fancy you can even add (+), (-), ...
PS: the inline is to get the functions working with all kinds of numeric values ;)
PPS: I played a bit and the problem is indeed (as mentioned in the link you gave - that you can only have a very limited set of "suffixes" - namely Q, R, Z, I, N, and G) - for example this kindof works:
module NumericLiteralQ =
open Domain
let inline FromZero() = Pixel 0.0<px>
let inline FromOne() = Pixel 1.0<px>
let inline FromString (s:string) =
System.Double.Parse s * 1.0<px> |> Pixel
let inline FromInt32 (n:int) =
1.0<px> * float n |> Pixel
let inline FromInt64 (n:int64) =
1.0<px> * float n |> Pixel
but I think it's very uggly to write
let p = 5Q
instead of
let p = pixel 5
or
let p = 5 |> pixel
I have the code to implement some geometric operations between primitives
type point = double * double
type shape =
| Point of point
| Line of point * point
| Vector of point
| Circle of point * double
with
member this.ToString = function
| Point (x,y) -> sprintf "(%f; %f)" x y
| Vector (x,y) -> sprintf "(%f; %f)" x y
| Line ((x0,y0),(x1,y1)) -> sprintf "(%f; %f)->(%f; %f)" x0 y0 x1 y1
| Circle ((x0,y0),radius) -> sprintf "(%f; %f)r%f" x0 y0 radius
let inline (-) (Point (x0,y0)) (Point (x1,y1)) = Vector (x0-x1,y0-y1)
let inline (+) (Point (x0,y0)) (Vector (x1,y1)) = Point (x0+x1,y0+y1)
And the compiler says that the pattern match on the operators is not exhaustive though this is only a warning. How can I correctly implement operators only between specific sub type of the DU without the compiler complaining?
Operators are typically defined as static members:
type shape =
...
static member (-) (x, y) =
match x, y with
| Point (x0,y0), Point (x1,y1) -> Vector (x0-x1,y0-y1)
| Point (x0,y0), Vector (x1,y1) -> Point (x0+x1,y0+y1)
| _ -> failwith "invalid arguments"
A few notes about your attempt:
union cases are not types, so they can't be used to define method overloads
functions can't be overloaded
As a side note, you've got another problem, which is that ToString should match on this, but right now matches on an anonymous argument (instead of having type unit -> string, it's shape -> string. Also, it should be declared with override, not member (which would also have pointed out that the signature is wrong).
The basic problem is that at compile time, the compiler does not know if which specific instance of shape you have chosen to create. As a result, any restriction must be done at run time, or by imposing additional constraints on the type. I think the most elegant solution with run time checking would be something like
type shape = ...
static member (-) (a,b) =
match (a,b) with
|Point(c,d),Point(e,f) -> ...
|Point(c,d),Vector(e,f) -> ...
| _ -> failwith "Can't add these shapes"
Alternatively, you could change shape to have point and vector as subtypes of a different DU as follows
type addable = |Point of point |Vector of point
and then modify shape accordingly.
I would do the following:
type PointT = double * double
type Shape =
| Point of PointT
| Line of PointT * PointT
| Vector of PointT
| Circle of PointT * double
with
member this.ToString = function
| Point (x,y) -> sprintf "(%f; %f)" x y
| Vector (x,y) -> sprintf "(%f; %f)" x y
| Line ((x0,y0),(x1,y1)) -> sprintf "(%f; %f)->(%f; %f)" x0 y0 x1 y1
| Circle ((x0,y0),radius) -> sprintf "(%f; %f)r%f" x0 y0 radius
let inline (-) (p0 : Shape) (p1 : Shape) : Shape option =
match p0, p1 with
| Point(x0, y0), Point(x1, y1) -> Some(Vector(x0 - x1, y0 - y1))
| _ -> None
let inline (+) (p0 : Shape) (p1 : Shape) : Shape option =
match p0, p1 with
| Point(x0, y0), Vector(x1, y1) -> Some(Point(x0 + x1, y0 + y1))
| _ -> None
I am playing with a toy problem (Convex hull identification) and needed lexicographic sorting twice already. One of the cases was given a list of type Point = { X: float; Y: float }, I would like to sort by X coordinate, and in case of equality, by Y coordinate.
I ended up writing the following:
let rec lexiCompare comparers a b =
match comparers with
[ ] -> 0
| head :: tail ->
if not (head a b = 0) then head a b else
lexiCompare tail a b
let xComparer p1 p2 =
if p1.X > p2.X then 1 else
if p1.X < p2.X then -1 else
0
let yComparer p1 p2 =
if p1.Y > p2.Y then 1 else
if p1.Y < p2.Y then -1 else
0
let coordCompare =
lexiCompare [ yComparer; xComparer ]
Which allows me to do
let lowest (points: Point list) =
List.sortWith coordCompare points
|> List.head
So far, so good. However, this feels a bit heavy-handed. I have to create specific comparers returning -1, 0 or 1, and so far I can't see a straightforward way to use this in cases like List.minBy. Ideally, I would like to do something along the lines of providing a list of functions that can be compared (like [(fun p -> p.X); (fun p -> p.Y)]) and do something like lexicographic min of a list of items supporting that list of functions.
Is there a way to achieve this in F#? Or am I thinking about this incorrectly?
Is there a way to achieve this in F#? Or am I thinking about this incorrectly?
F# does this for you automatically when you define a record type like yours:
> type Point = { X: float; Y: float };;
type Point =
{X: float;
Y: float;}
You can immediately start comparing values. For example, defining a 3-element list of points and sorting it into lexicographic order using the built-in List.sort:
> [ { X = 2.0; Y = 3.0 }
{ X = 2.0; Y = 2.0 }
{ X = 1.0; Y = 3.0 } ]
|> List.sort;;
val it : Point list = [{X = 1.0;
Y = 3.0;}; {X = 2.0;
Y = 2.0;}; {X = 2.0;
Y = 3.0;}]
Note that the results were sorted first by X and then by Y.
You can compare two values of any comparable type using the built-in compare function.
If you want to use a custom ordering then you have two options. If you want to do all of your operations using your custom total order then it belongs in the type definition as an implementation of IComparable and friends. If you want to use a custom ordering for a few operations then you can use higher-order functions like List.sortBy and List.sortWith. For example, List.sortBy (fun p -> p.Y, p.X) will sort by Y and then X because F# generates the lexicographic comparison over 2-tuples for you (!).
This is one of the big advantages of F#.
Well, to start with, you can rely on F#'s built-in compare function:
let xComparer p1 p2 = compare p1.X p2.X
let yComparer p1 p2 = compare p1.Y p2.Y
Alternatively, you can clearly abstract this a bit if desired:
let compareWith f a b = compare (f a) (f b)
let xComparer = compareWith (fun p -> p.X)
let yComparer = compareWith (fun p -> p.Y)
Or, as you note, you could build this approach directly into the list handling function:
let rec lexiCompareWith l a b =
match l with
| [] -> 0
| f::fs ->
match compare (f a) (f b) with
| 0 -> lexiCompareWith fs a b
| n -> n
One important limitation here is that since you're putting them into a list, the functions must all have identical return types. This isn't a problem in your Point example (since both functions have type Point -> float), but it would prevent you from sorting two Person objects by name and then age (since the first projection would have type Person -> string but the second would have type Person -> int).
I don't think I understand your question correctly, but doesn't the following code work fine?
let lowest (points : Point list) = List.sort points |> List.head
It seems that F# performs implicit comparison on record data types. And my little experiment indicates that the comparison happens to be lexicographic. But I could not find any evidence to support that result.
So I'm not yet sure F# compares records lexicographically. I can still write in the following manner using tuple instead:
let lowest (points : Point list) =
let tuple = List.map (fun pt -> (pt.X, pt.Y)) points |> List.sort |> List.head
{ X = fst tuple; Y = snd tuple }
I hope this post could help.
How do I match union cases dynamically in F# when there are value declarations?
Non working code:
let myShape = Shape.Square
expect myShape Shape.Circle
type Shape =
| Circle of int
| Square of int
| Rectangle of ( int * int )
let expect someShape someUnionCase =
if not ( someShape = someUnionCase )
then failwith ( sprintf "Expected shape %A. Found shape %A" someShape someUnionCase )
let myShape = Shape.Square
expect myShape Shape.Circle // Here I want to compare the value types, not the values
If my union cases did not declare values, this works using instantiation samples (which is not what I want):
let myShape = Shape.Square
expect myShape Shape.Circle
type Shape =
| Circle
| Square
| Rectangle
let expect someShape someUnionCase =
if not ( someShape = someUnionCase )
then failwith ( sprintf "Expected shape %A. Found shape %A" someShape someUnionCase )
let myShape = Shape.Square
expect myShape Shape.Circle // Comparing values instead of types
Interestingly, this can be done very easily in C#, but the F# compiler will not allow you to call the functions - which seems odd.
The spec says that a discriminated union will have (section 8.5.3):
One CLI instance property u.Tag for each case C that fetches or
computes an integer tag corresponding to the case.
So we can write your expect function in C# trivially
public bool expect (Shape expected, Shape actual)
{
expected.Tag == actual.Tag;
}
It is an interesting question as to why this can't be done in F# code, the spec doesn't appear to give a good reason why.
When you call the expect function in your example with e.g. Shape.Square as an argument, you're actually passing it a function that takes the arguments of the union case and builds a value.
Analyzing functions dynamically is quite difficult, but you could instead pass it concrete values (like Shape.Square(0)) and check that their shape is the same (ignore the numeric arguments). This can be done using F# reflection. The FSharpValue.GetUnionFields function returns the name of the case of an object, together with obj[] of all the arguments (which you can ignore):
open Microsoft.FSharp.Reflection
let expect (someShape:'T) (someUnionCase:'T) =
if not (FSharpType.IsUnion(typeof<'T>)) then
failwith "Not a union!"
else
let info1, _ = FSharpValue.GetUnionFields(someShape, typeof<'T>)
let info2, _ = FSharpValue.GetUnionFields(someUnionCase, typeof<'T>)
if not (info1.Name = info2.Name) then
failwithf "Expected shape %A. Found shape %A" info1.Name info2.Name
If you now compare Square with Circle, the function throws, but if you compare two Squares, it works (even if the values are different):
let myShape = Shape.Square(10)
expect myShape (Shape.Circle(0)) // Throws
expect myShape (Shape.Square(0)) // Fine
If you wanted to avoid creating concrete values, you could also use F# quotations and write something like expect <# Shape.Square #> myValue. That's a bit more complex, but maybe nicer. Some examples of quotation processing can be found here.
I use the same pattern to implement type checking in HLVM. For example, when indexing into an array I check that the type of the expression is an array ignoring the element type. But I don't use reflection as the other answers have suggested. I just do something like this:
let eqCase = function
| Circle _, Circle _
| Square _, Square _
| Rectangle _, Rectangle _ -> true
| _ -> false
Usually in a more specific form like this:
let isCircle = function
| Circle _ -> true
| _ -> false
You could also do:
let (|ACircle|ASquare|ARectangle|) = function
| Circle _ -> ACircle
| Square _ -> ASquare
| Rectangle _ -> ARectangle
If you do decide to go the reflection route and performance is an issue (reflection is unbelievably slow) then use the precomputed forms:
let tagOfShape =
Reflection.FSharpValue.PreComputeUnionTagReader typeof<Shape>
This is over 60× faster than direct reflection.
NOTE this has a caveat. See UPDATE below.
It appears that union cases are implemented as nested classes of the union type (type name: FSI_0006+Shape+Square). So given a union type instance, checking the type of the instance by obj.GetType() is sufficient.
let expect (someShape:'T) (someUnionCase:'T) =
if (someShape.GetType() <> someUnionCase.GetType()) then failwith "type not compatible"
type Shape =
| Circle of int
| Square of int
| Rectangle of ( int * int )
let myShape = Shape.Square 12
printfn "myShape.GetType(): %A" (myShape.GetType())
expect myShape (Shape.Circle 5)
This outputs:
myShape.GetType(): FSI_0006+Shape+Square
System.Exception: type not compatible
at Microsoft.FSharp.Core.Operators.FailWith[T](String message)
> at FSI_0006.expect[T](T someShape, T someUnionCase)
at <StartupCode$FSI_0006>.$FSI_0006.main#()
Stopped due to error
I just don't know if this approach is considered implementation dependent, i.e., some platform/runtime implements this differently such that the types of two different union case objects are the same.
UPDATE
OK I found the above doesn't work for union type with cases that don't take parameters. In that case, the implementation of the cases are different and .GetType() always gives the union type's declaring type. The below code demonstrates this:
type Foo = A|B|C
type Bar = X|Y|Z of int
let getType (x:obj) = x.GetType()
let p (x:obj) = printfn "%A" x
A |> getType |> p
B |> getType |> p
C |> getType |> p
X |> getType |> p
Y |> getType |> p
Z 7 |> getType |> p
This gives:
FSI_0004+Foo
FSI_0004+Foo
FSI_0004+Foo
FSI_0004+Bar+_X
FSI_0004+Bar+_Y
FSI_0004+Bar+Z
The more general alternative, as mentioned in another answer, would be to convert the case instances into tags:
open Microsoft.FSharp.Reflection
// more general solution but slower due to reflection
let obj2Tag<'t> (x:obj) =
FSharpValue.GetUnionFields(x, typeof<'t>) |> fst |> (fun (i: UnionCaseInfo) -> i.Tag)
[A;B;C;A] |> List.map obj2Tag<Foo> |> p
[X;Y;Z 2; Z 3; X] |> List.map obj2Tag<Bar> |> p
This gives:
[0; 1; 2; 0]
[0; 1; 2; 2; 0]
This should be considerably slower if operated on large amount of objects, as it's heavily depend on reflection.