4

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) ?

Functional_S
  • 1,159
  • 6
  • 9
  • 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 Jan 05 '16 at 20:49
  • @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 Jan 05 '16 at 20:59
  • 2
    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 Jan 05 '16 at 21:22
  • @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 Jan 05 '16 at 21:35
  • 1
    @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 Jan 05 '16 at 22:29
  • @kvb your wildcard comment is the answer. – Functional_S Jan 05 '16 at 22:38
  • @kvb What's the idiomatic way to do it? – ca9163d9 Jan 06 '16 at 18:46
  • @dc7a9163d9 - Total active patterns that throw are strange - why not use a partial active pattern (and then let the fact that the cases aren't all covered be obvious at compile time)? Furthermore, in the case of something like `StartsWithX` I'd expect the pattern to return the remainder of the string, not the whole string (and I'd expect `NonEmptyString` to just return `()`). – kvb Jan 06 '16 at 19:14

1 Answers1

1

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!

Functional_S
  • 1,159
  • 6
  • 9