Verifying that three cards make a “Set”

The game of Set involves cards with four properties: number, symbol, shading, and colour.

Three cards make a "Set" if the property is either all the same or all different across the three cards, for each of the four properties.

I wrote some code to verify if three cards constitute a "Set". But I'm not satisfied with the final function isSet. I feel there should be some way to iterate over the four properties instead of writing four separate lines. Any help would be appreciated - I'm fairly new to F#.

type Number = | One | Two | Three
type Symbol = | Round | Wavy | Diamond
type Shading = | Empty | Lines | Full
type Colour = | Red | Green | Blue

type Card = {number: Number;
symbol: Symbol;
colour: Colour}

let all =
List.reduce (=)

let numUnique list =
list
|> List.distinct
|> List.length

let allSame list =
list
|> numUnique
|> (=) 1

let allUnique list =
let length = List.length list
list
|> numUnique
|> (=) length

let allSameOrUnique list =
allSame list || allUnique list

let isSet card1 card2 card3 =

let check1 = allSameOrUnique [card1.number; card2.number; card3.number]
let check2 = allSameOrUnique [card1.symbol; card2.symbol; card3.symbol]
let check4 = allSameOrUnique [card1.colour; card2.colour; card3.colour]

all [check1; check2; check3; check3; check4]

let card1 = {number = Two; symbol = Round; shading = Lines; colour = Green}
let card2 = {number = One; symbol = Round; shading = Empty; colour = Green}
let card3 = {number = Three; symbol = Round; shading = Full; colour = Green}

The final function returns a boolean for verifying the set:

> isSet card1 card2 card3

val it : bool = true

I feel there should be some way to iterate over the four properties instead of writing four separate lines.

You can use a higher-order function that takes in a function that returns a card property. Here is an example of how that could look. I've also simplified the code to the extent that none of the helper functions are needed, and changed some formatting to more idiomatic F#:

type Number = One | Two | Three
type Symbol = Round | Wavy | Diamond
type Shading = Empty | Lines | Full
type Colour = Red | Green | Blue

type Card =
{ Number: Number
Symbol: Symbol
Colour: Colour}

let isSet card1 card2 card3 =
let sameOrUniqueBy prop =
let unique = [ card1; card2; card3 ] |> List.distinctBy prop |> List.length
unique = 1 || unique = 3

sameOrUniqueBy (fun c -> c.Number)
&& sameOrUniqueBy (fun c -> c.Symbol)
&& sameOrUniqueBy (fun c -> c.Shading)
&& sameOrUniqueBy (fun c -> c.Colour)

In this code, sameOrUniqueBy is a higher-order function because it takes a function as an argument. It is defined as a local function inside isSet so that the cards don't have to be passed in each time it's used.

Testing:

let card1 = { Number = Two; Symbol = Round; Shading = Lines; Colour = Green }
let card2 = { Number = One; Symbol = Round; Shading = Empty; Colour = Green }
let card3 = { Number = Three; Symbol = Round; Shading = Full; Colour = Green }

isSet card1 card2 card3 = true // ✔

isSet card1 card1 card3 = false // ✔