Can you pattern match integers to ranges in OCaml?

825 views Asked by At

As part of Graham's Scan, I am writing a function that determines whether the line is turning left or right at a certain point. Considering we have a function determinant point -> point -> point -> int where determinant p0 p1 p2 returns det(p0p1, p0p2), here is the code :

(* Determines how the line turns at s, when going from t to p*)
let turn t p s =
  let det = det s p t in 
  if det < 0 then
    'r' (* Turning right *)
  else if det > 0 then
    'l'(* Turning left *)
  else 
    's' (* Going straight ahead *)
;;

Is there any way to do this with pattern matching instead ? Here is what I have tried, which obviously doesn't work :

let turn t p s =
  match det s p t with
  | < 0 -> 'r'
  | 0 -> 's'
  | > 0 -> 'l'
  
;;

( Note that the variable / function names are determined by the class ; while I do find single letter names slightly confusing, I may not change them... )

2

There are 2 answers

0
Chris On BEST ANSWER

No.

Well...

Conditional guards on patterns could do what you're suggesting.

let turn t p s =
  let det = det s p t in 
  if det < 0 then
    'r' (* Turning right *)
  else if det > 0 then
    'l'(* Turning left *)
  else 
    's' (* Going straight ahead *)'

Would become:

let turn t p s =
  match det s p t with
  | n when n < 0 -> 'r'
  | n when n > 0 -> 'l'
  | _            -> 's' 

Which of these expresses what's going on more cleanly is a matter of opinion.

What about...?

The argument becomes more interesting with more complex data to match on. A very contrived example with far better actual implementations follows.

type num = Int of int | Float of float

let rec cmp a b =
  match a, b with
  | Int a, Int b ->
    if a < b then -1
    else if a > b then 1
    else 0
  | Float a, Float b -> 
    if a < b then -1
    else a > b then 1
    else 0
  | Int a, Float b ->
    let a = float_of_int a in
    if a < b then -1
    else a > b then 1
    else 0
  | Float a, Int b ->
    let b = float_of_int b in
    if a < b then -1
    else a > b then 1
    else 0

Using conditional guards:

let rec cmp a b =
  match a, b with
  | Int a, Int b when a < b                -> -1
  | Int a, Int b when a > b                ->  1
  | Int a, Int b                           ->  0
  | Float a, Float b when a < b            -> -1
  | Float a, Float b when a > b            ->  1
  | Float a, Float b                       ->  0
  | Int a, Float b when float_of_int a < b -> -1
  | Int a, Float b when float_of_int a > b ->  1
  | Int a, Float b                         ->  0
  | Float a, Int b when a < float_of_int b -> -1
  | Float a, Int b when a > float_of_int b ->  1
  | _                                      ->  0

Still a matter of opinion, but conditional guards can help to "flatten" nested conditionals in pattern-matching.

0
octachron On

Integer ranges are only supported for char currently (up to OCaml 5.0). And even for char the support is in fact partial: the pattern 'a'..'d' is nothing more than a syntactic sugar for 'a'|'b'|'c'|'d'. But such encoding can only be used for small integer types like char: exploding the interval 0..int_max into int_max pattern is not an option for 63-bits integers.

It would be possible to support full integer ranges (or ranges on fully ordered primitives types) in the pattern matching compiler, but this would require a non-trivial amount of work that has not happened yet.