The definition and use of an active pattern are completely separate issues.

Defining an active pattern is exactly like defining a function that has a funny name (e.g., (|Foo|Bar|)) so anything goes on the right-hand side. The only thing that is active-patterns-aware here is the mapping of the tags Foo and Bar to the appropriate "anonymous" tags in the Choice type.

When you use an active pattern in a match block, the compiler looks up the function that defines it. E.g., if you use the tag Foo, it will lookup and find the function (|Foo|Bar|). Then it expects this function to conform to a certain type. In this case the result type of the function should be Choice2<_,_>. If its type doesn't unify then you get the compile error here.

Partial multi-case patterns (e.g., (|Foo|Bar|_|)) are not supported by design. You can always define them as function but you'll always get an error when you go to use them.

What I've described is the current implementation. It could change. It is easy to argue that it would be better to check the active pattern at the definition-site not the use-site.

By on 3/12/2009 6:26 PM ()

Hello,

your second example is very interesting:
F# specification says: partial active pattern should return choice type, and for case it returns Some a, that result of computation (a) will be used as resolved value, and for case it returns None, original value (applied to active patern function) will be applied to next matching rules.
Can F# team give some explanations?

By on 3/12/2009 12:15 PM ()

Your first example is known as a total recognizer or affectionally as a"banana split" (the tags around it look like bananas ;). In the example you give, there's not much point: you should return values (if you have a match) using the tags in the banana split. Say (didn't compile this, experiment a bit):

1
let (|Pos|Neg|) x =  if x >= 0 then Pos(x,1) else Neg(x,-1)

 Note that both results do not necessarily need to have the same shape. But for any value of the type, you have a match. Hence the total in total recognizer..Your second example is known as a "banana slice", and is useful when the decomposition of your value is not total, i.e. the pattern match does not match all possible values. 

1
2
let (|MulThree|_|) inp =
if inp % 3 = 0 then Some(inp/3) else None

 In that case the compiler should check that you return an option type. Not sure why the compiler does not complain in your first example.Check out this paper for many more examples and the two other kinds of active patterns:[link:research.microsoft.com]

By on 3/12/2009 12:55 PM ()

Thanks Kurt - but I think I wasn't explicit enough about my question. I understand the normal uses of these constructions (or, at least I think I do). What puzzled me is what happens when you do something outside the regular use cases.

Your first example is known as a total recognizer

or affectionally as a"banana split" (the tags around it look like

bananas ;). In the example you give, there's not much point: you should

return values (if you have a match) using the tags in the banana split.

Is there any use

for the construction I gave (returning something unrelated to the

tags)? Seems like the answer is no - it compiles, but it's useless. Well, I suppose it's not completely useless, since you can call active patterns as normal functions:

1
2
3
4
5
6
7
let (|Foo|Bar|) x =
 "BAD DOG!"

let t = (|Foo|Bar|) 3;;

> t;;
val it : string = "BAD DOG!"

But I've never seen call-an-active-pattern-as-a-function talked about anywhere, and it seems unlikely to be useful, even it does work.

Your second example is known as a "banana slice", and is useful when

the decomposition of your value is not total, i.e. the pattern match

does not match all possible values.

1
2
let (|MulThree|_|) inp =
if inp % 3 = 0 then Some(inp/3) else None

Right, I understand that this is the normal way to use these. There were two weird things in my sample, though. In your example, you have a single tag (Multhree) followed by an underscore. In my code, I had two tags followed by an underscore, and then a return of something other than an option. I can't figure out why those would ever be useful.The answer for both of my original questions may very well be "those are completely useless bits of code - don't do that." I just wanted to make sure.

By on 3/12/2009 4:29 PM ()

Thanks Kurt - but I think I wasn't explicit enough about my question.  I understand the normal uses of these constructions (or, at least I think I do).  What puzzled me is what happens when you do something outside the regular use cases.

I see. Well, what gneverov said. Your examples show nicely that there is little special about active patterns: they are just like first class functions. So you can abstract over them, and even pass them in as parameters to other functions or active patterns (see the link I gave for a cool - and useful - example of that). Other than this educational value I don't see why I would write any active patterns like the one you propose, but of course my imagination may be falling short here ;)Kurt 

By on 3/13/2009 1:39 AM ()

Thanks -

A combination of kurt and gneverov's comments, plus reading the paper Kurt suggested

([link:research.microsoft.com] - Combining

Total and Ad Hoc Extensible Pattern

Matching in a Lightweight Language Extension, Don Syme, Gregory

Neverov, and James Margetson) helped me understand things quite a bit

better. That paper's well worth reading for the explanations of the various flavors of bananas alone.

The paper did point out one more bit of patterns that I still don't understand. How does the (|Q|_|) match against the (|SomePattern|_|) that's passed in to it?

1
2
3
4
5
6
7
8
9
10
11
12
let Fnord (|Q|_|) inp =
  match inp with 
  | Q -> true
  | _ -> false
  
let (|SomePattern|_|) x =
  printfn "x is %A in the |SomePattern| recognizer" x
  Some()
    
let y = Fnord (|SomePattern|_|) 10

printfn "y is %A" y
By on 3/15/2009 3:42 PM ()

Hi,
I think the way to understand this is to realize that when you declare active pattern, you're just declaring a function with a weird name "|SomePattern|_|" (the parens around the name are used just like when declaring custom operators that also have weird name). This means that you can use active patterns as ordinary functions:

1
2
3
4
5
6
7
 
> let (|A|_|) a = None;;
val ( |A|_| ) : 'a -> 'b option
> let f = (|A|_|);;
val f : 'a -> 'b option
> let (|B|_|) = f;;
val ( |B|_| ) : ('a -> 'b option)

So, your "Fnord" function just takes a function as an argument, but since the function has the "right active pattern type" it can reinterpret it as an active pattern, which gives you "pattern names" usable in pattern matching.

By on 3/15/2009 7:53 PM ()

Hi,
I think the way to understand this is to realize that when you declare active pattern, you're just declaring a function with a weird name "|SomePattern|_|" (the parens around the name are used just like when declaring custom operators that also have weird name). This means that you can use active patterns as ordinary functions:

1
2
3
4
5
6
7
 
> let (|A|_|) a = None;;
val ( |A|_| ) : 'a -> 'b option
> let f = (|A|_|);;
val f : 'a -> 'b option
> let (|B|_|) = f;;
val ( |B|_| ) : ('a -> 'b option)

So, your "Fnord" function just takes a function as an argument, but since the function has the "right active pattern type" it can reinterpret it as an active pattern, which gives you "pattern names" usable in pattern matching.

That makes sense.

And in this case, the "right active pattern type" is just something that has the type ('a -> unit option) - like I was told earlier in this thread, there's nothing special about the results of the active pattern function declaration syntax. This worked just fine:

1
2
3
4
5
6
7
 
let Fnord (|Q|_|) inp =
  match inp with
  | Q -> true
  | _ -> false
let functionReturningSomeUnit = fun x -> Some(x)
let fnord2 = Fnord functionReturningSomeUnit ()

So it seems like something that looks like an active pattern (an identifier in banana bars), when it's used as a function argument (the (|Q|_|) in "let Fnord (|Q|_|) inp ="), does two things:

1. It helps set the right arity for the function, and
2. It defines anonymous tags that can be used in the function body.

So it's not quite the same as a regular bound value, since there's no way that I could figure out to get the actual value of the function passed in to that place. (In my code above, I couldn't figure out how I could get at the value of functionReturningSomeUnit, instead of just the tags. Not that I can think of a real use case for doing that, but I was curious.)

Total patterns seem to be the same, where you just pass in something with the right ChoiceX_X signature:

1
2
3
4
5
6
7
8
let Fnord (|Q|Z|) inp =
  match inp with
  | Q -> true
  | _ -> false
// >> val Fnord : ('a -> Choice<unit,'b>) -> 'a -> bool

let functionReturningChoice (x: unit) = Choice2_1(x)
let y = Fnord functionReturningChoice ()
By on 3/15/2009 9:55 PM ()

So it seems like something that looks like an active pattern (an identifier in banana bars), when it's used as a function argument (the (|Q|_|) in "let Fnord (|Q|_|) inp ="), does two things:

I'm not sure if I understand correctly, but I think this is possible:

1
2
3
4
5
6
7
8
9
 
let Foo (|Q|_|) x =
  // Using pattern names...
  match x with
  | Q n -> printfn "%d" n // 'n' is int
  | _ -> ()
  // Using the bound value as a standard function
  let res = (|Q|_|) x 
  printfn "%A" res;; // 'res' is option<int>

Now you can call it using function/active pattern as an argument.
For example, you can even do this (id is identity function that just returns the argument):

1
2
3
4
5
6
 
> Foo id (Some 10);;
10
Some 10
> Foo id None
<null>
By on 3/16/2009 4:46 AM ()

I can get these expressions to compile. But, I can't seem to make any use of them compile.

In the first case, the compiler seems to not like the fact that a choice is not returned.

In the second case, except for not returning a choice, it seems reasonable. But, I get a compiler error on usage there too: ``partial active patterns may only generate one result.''

By on 3/11/2009 1:00 PM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper