3
votes

We have this small helper functions

open System

let (|NonEmptyString|) value =
    if String.IsNullOrEmpty value
    then failwith "String must be non-empty."
    else value

let (|StartsWithX|) (value:string) =
    if value.StartsWith("X") |> not
    then failwith "String must start with X."
    else value

And using the active pattern matching function NonEmptyString in the function interface works fine:

let hi (NonEmptyString s) = printfn "Hi %s" s

hi "X"  // Hi X
hi ""   // System.Exception: String must be non-empty.

Now to the problem.

It would be nice to combine some validators together as a more complex validation constraint, like so

let hi (NonEmptyString s  &  StartsWithX s) = printfn "Hi %s" s
// error FS0038: 's' is bound twice in this pattern

If only one 's' is allowed, we can think of combining the functions so we have only one argument s. And because active pattern matching functions are functions of course, applying the function composition operator >> but it does not fit here.

let hi (NonEmptyString >> StartsWithX s) = printfn "Hi %s" s
// error FS0010: Unexpected infix operator in pattern

The question is, how can we do that (in F# 4.0) ?

1
Any way to do this is going to be a little hacky. Why not do the check on the other side of the equals sign? - John Palmer
@JohnPalmer Because so it's very dense readable descriptive code. The validation part is out of the function and the interface is descriptive. - Functional_S
Just use a wildcard: let hi (NonEmptyString s & StartsWithX _) = printfn "Hi %s" s. But what you're doing doesn't look like very idiomatic F# to me. - kvb
@kvb Aha, type inference will find s for the wildcard. I'm just experimenting. To do it the idiomatic F# way, I would use single case ADT as types. But I'm experimenting with composition here. Thanks, up-voted your comment - Functional_S
@Functional_S - since the patterns just return the input unmodified as output, you can also just literally compose the patterns: let hi (NonEmptyString (StartsWithX s)) = .... That won't generalize to more idiomatic uses of active patterns, though. - kvb

1 Answers

0
votes

As @kvb commented, the wildcard _ helps in the AND case

// AND
let hi (NonEmptyString _  &  StartsWithX s) = printfn "Hi %s" s

The OR case works with with two 's' bindings

// OR
let hi (NonEmptyString s  |  StartsWithX s) = printfn "Hi %s" s
// The logic here makes less sense. It's kept simple with the given helper functions from above.  

Note:

This is just for experimenting with composition of Active Pattern Matching as code contracts in a descriptive way at the function interface.

This can be seen as use or misuse of Active Pattern Matching (idiomatic F# or not), it's your choice!