11
votes

I've just noticed F# allows me to use let bindings with literals and other patterns as follows:

let fib 0 = 1
let exists item [] = false
let car (hd :: tl) = hd
let cdr (hd :: tl) = tl

F# correctly interprets these functions as a kind of pattern matching, because gives me the following warnings:

Warning 1 Incomplete pattern matches on this expression. For example, the value '1' will not be matched

Warning 2 Incomplete pattern matches on this expression. For example, the value '[_]' will not be matched

etc.

These functions work as expected, but I want to define a function in this style with complete pattern matches, however I can't find anything about this alternative pattern matching syntax in the F# manual.

I know I can use let whatever = function ... and let whatever x = match x with ... to get the results I want, but I've just discovered yet another syntax for pattern matching and it'll nag at me forever if I don't figure out how to use it.

How do I write functions using the alternative pattern matching syntax shown above?

4

4 Answers

12
votes

AFAIK, there is no way in F# to declare multiple let bindings with the same name and different pattern matching signatures. I believe the closest construct to what you are looking for is a function rules expression.

Take this example for car

let car = function
    | hd::tl -> hd
    | [] -> failwith "empty list"
9
votes

JaredPar is right, F# doesn't have the syntactic form that Haskell does here.

The F# form is mostly useful for breaking open single-case discriminated unions or for defining functions with incomplete matches (like your 'car' example that fails on the empty list). It's simply a consequence of the fact that practically all name-binding in the language is done via patterns; this syntactic form (defining a function using patterns on arguments) is not often too useful in practice, for the exact reason you described.

I think Haskell did a number of things better than ML when it comes to syntactic forms, but F#'s roots are in ML. The benefit is that a good subset of F# cross-compiles with OCaml (which helped bootstrap the F# language and user community); the drawback is that F# is 'stuck' with a few bits of ugly/limited syntax.

8
votes

Apparently, F#'s pattern matching is way more powerful than we use in common development.

First, you may bind several values at once. Typically, you will do it with List.partition:

let data = [7;0;0;0;1;0;1;1;1;1;0;0;1;1;0]
let ones, others = data |> List.partition ((=) 1) // bind two values

As a side note, you may bind several identifiers to the same value:

let (a & b) = 42 // a = 42; b = 42

Let's start with a simple let binding for sake of simplicity.

let hd::tl = data

warning FS0025: Incomplete pattern matches on this expression. For example, the value '[]' may indicate a case not covered by the pattern(s).

To mitigate this, we have to add another case for empty list:

let (hd::tl) | [] = data

error FS0018: The two sides of this 'or' pattern bind different sets of variables

And that's true; in case of empty list, hd and tl remain unbound. It's easy to bind tl with the same empty list:

let (hd::tl) | ([] as tl) = data

However, the error error FS0018 does not go away. Indeed, we also have to provide with some "default" value for hd.
The following dirty trick will do this.

let (hd::tl, _) | ([] as tl , hd) = data, 42

The line above will bind hd to the data's head, in case if the list is not empty, or with the extra value provided in the second value of the tuple.

Note I have not found the way to embed 42 into the let construct.

Finally, the same for car function:

let car ((hd::tl, _) | ([] as tl, hd)) = hd
let foo = car(data, 42) // foo = 7
let bar = car([], 42)   // bar = 42
1
votes

How do I write functions using the alternative pattern matching syntax shown above?

As you have but only when one pattern is exhaustive. Obvious examples where this is useful include tuples and records as well as single-case unions and active patterns.

You use this language feature whenever you do:

let f(a, b) = ...

So this generalizes to:

let f(a, (b, c)) = ...

You can even use this to choose between a default value or Some value:

let def(x, None | _, Some x) = x

BTW, the style you are suggesting was used in SML before Haskell and SML is an ML so this obviously has nothing to do with Haskell vs ML. I actually prefer the OCaml/F# style because it is less repetitive:

fun descriptiveFunctionName [] = true
fun descriptiveFunctionName (_::_) = false

vs

let descriptiveFunctionName = function
  | [] -> true
  | _::_ -> false

This is better for non-academic code where there is real value is using self-documenting identifiers.