Emulating Prolog backtracking in F# - parsing

I am currently involved in a project to develop an application able to consider a set of nodes and connections and find the shortest path (a common and well-known issue) between two nodes (on allowed connections). Well I don't really have to build an application from zero, but just need to "convert" a Prolog pre-existing application in f#.
I thought I bit about it and finally asked myself one question: "Instead of developing a special purpose solution and implementing new algorithms specifically binded to this problem, can I create a program able to accept facts like Prolog and use them to make queries or something similar?".
By doing so I would create a set of facts (like in Prolog) and then use them to make queries.
So, considering now this new issue (converting Prolog in F#) I need to find a way to create facts like these:
myfact1(el1, el2,..., eln).
myfact2(el1, el2,..., elm).
...
myfactk(el1, el2,..., elp).
to something in a similar syntax like:
fact factname1: el1 el2 ... eln;
fact factname2: el1 el2 ... elm;
...
fact factnamek: el1 el2 ... elp;
I know that F# is very well for parsing, so I think that parsing this would probably not be a problem.
OK! Now that it is parsed, I should define an algorithm that, while parsing the code, stores all facts in some sort of knowledge (nothing more than a table). In order to make then all needed associations.
For example a solution might be a hashtable that considers all associations
factname1 -> el1
factname1 -> el2
...
factname1 -> eln
factname2 -> el1
factnale2 -> el2
...
factname2 -> elm
factname3 -> el1
...
...
factnamek -> el1
factnamek -> el2
...
factnamek -> elp
By doing so I will always be able to solve queries.
For example consider the following Prolog fact
mother(A, B) % This means that A is mother of B
mother(C, D)
In F# I create
fact mother: A B;
fact mother: C D;
My hashtable is:
mother -> A | B
mother -> C | D
The first col is the fact name and the second is the value (here a tuple).
If I want to search: "who is the mother of B" --> I search for mother, and look for value, I find B, I look in the tuple and discover A!
Well it seems working.
But facts are easy to implement. What about rules?
For example rule parents:
parents(A, B, C) :- mother(A, C), father (B, C)
In F# using my syntax? Well I came up with this idea:
rule parents: A, B, C => mother A C and father B C
When my parser encounters a rule, what should it do? I would like to create some sort of record in a table like I did and be able, later, to make queries in order to specify a subject and get its parents or specify a father and get all sons and so on...
What would you do?

There was a similar question about integrating Prolog-like programs into F# recently.
F# doesn't have any built-in support for performing search based on backtracking (like Prolog). You have essentially two options:
Re-implement the algorithm in F# using recursion and encoding backtracking yourself.
Implementing a Prolog interpreter and representing facts using some discriminated union.
To implement shortest path search, I would probably just implement the algorithm directly in F# (using functional programming will be quite convenient and there is no particular reason for using Prolog).
If you wanted to implement an interpreter, you'd probably use a discriminated union that allows you to rewrite your example with parents like this:
type Var = Var of string
type Expression =
| Binary of string * Expression * Expression
| Fact of string * Expression list
| Ref of Var
type Rule =
| Rule of string * Var list * Expression
/// Simplified syntax for writing And
let And(a, b) = Binary("and", a, b)
let a, b, c = Var("A"), Var("B"), Var("C")
Rule("parents", [a; b; c],
And(Fact("mother", [Ref a; Ref c]), Fact("father", [Ref b; Ref c])))

One good place to start is to look at implementing a Prolog-style unification algorithm in F#. Good pseudo-code or LISP implementations can be found in a number of general A.I. textbooks or on the web. You can work outwards from that to the backtracking and syntax. A unification algorithm should be fairly straightforward to implement in a feature-rich language like F# (though perhaps a bit arcane).

Once upon a time, I knew a guy who wrote an EDSL for Prolog in C++. He keeps meaning to do the same thing for F#, but never quite finds the time. He seems to recall the basic prolog unification & backtracking algorithm is very straightforward (an exercise in a late chapter of a popular Scheme text, perhaps?) and hopes someone will beat him to the punch, since he's on vacation. :)

Related

Curried functions and function application in Z3

I'm working on a language very similar to STLC that I'm converting to Z3 propositions, hence a few (sub)questions about how Z3 treats (uninterpreted) functions:
Functions are naturally curried in my language and as I'm converting the terms of my language recursively, I'd like to be able to build the corresponding Z3 AST recursively as well. That is, when I have a term f x y I'd like to first apply f to x and then apply that to y. Is there a way to do this? The API I've found so far (Z3_mk_func_decl/Z3_mk_app) seems to require me to collect all arguments first and apply them all at once.
Is there a reasonable way to represent something like (if b then f else g) x?
In both cases, I'm totally fine with functions being uninterpreted and restricting the reasoning to things like "b = True /\ f x = 0 => (if b then f else g) x = 0 holds".
SMTLib (as described in http://smtlib.cs.uiowa.edu/papers/smt-lib-reference-v2.6-r2017-07-18.pdf) is a many-sorted first-order logic. All functions (uninterpreted or not) must be applied to all its arguments, and you cannot have any form of currying. Also, you cannot do higher-order if-then-else, i.e., the branches of an if-then-else will have to be first-order values. (However, they can be arrays, and you can imagine "faking" functions with arrays. But that's besides the point.)
It should be noted that the next iteration of SMTLib (v3) will be based on a higher-order logic, at which point features like you're asking might become available. See: http://smtlib.cs.uiowa.edu/version3.shtml. Of course, this is still a proposal and it'll take a while before it's settled and actual solvers start implementing it faithfully. Eventually that'll happen, but I wouldn't expect it in the very near term.
Aside: Since you mentioned STLC (simply-typed-lambda-calculus), I presume you might be familiar with functional languages like Haskell. If that's the case, you might want to look into using SBV: https://hackage.haskell.org/package/sbv. It provides a framework for doing some of these things by carefully translating them away behind the scenes. Here's an example:
Prelude Data.SBV> sat $ \b -> (ite b (uninterpret "f") (uninterpret "g")) (0::SInteger) .== (0::SInteger)
Satisfiable. Model:
s0 = True :: Bool
f :: Integer -> Integer
f _ = 0
g :: Integer -> Integer
g _ = 2
Here we created two functions and used the ite construct to "merge" them; and got the solver to return us a model. Behind the scenes, SBV will fully saturate these applications and let you "pretend" you're programming in a higher-order sense, just like in STLC or Haskell. Of course, the devil is in the details and there are limitations to the approach, but modeling STLC in Haskell is a classic pastime for many people and doing it symbolically using SBV can be a fun exercise.

Partially deferred computation builder

I'm trying to work out how to use a computation builder to represent a deferred, nested set of steps.
I've got the following so far:
type Entry =
| Leaf of string * (unit -> unit)
| Node of string * Entry list * (unit -> unit)
type StepBuilder(desc:string) =
member this.Zero() = Leaf(desc,id)
member this.Bind(v:string, f:unit->string) =
Node(f(), [Leaf(v,id)], id)
member this.Bind(v:Entry, f:unit->Entry) =
match f() with
| Node(label,children,a) -> Node(label, v :: children, a)
| Leaf(label,a) -> Node(label, [v], a)
let step desc = StepBuilder(desc)
let a = step "a" {
do! step "b" {
do! step "c" {
do! step "c.1" {
// todo: this still evals as it goes; need to find a way to defer
// the inner contents...
printfn "TEST"
}
}
}
do! step "d" {
printfn "d"
}
}
This produces the desired structure:
A(B(C(c.1)), D)
My issue is that in building the structure up, the printfn calls are made.
Ideally what I want is to be able to retrieve the tree structure, but be able to call some returned function/s that will then execute the inner blocks.
I realise this means that if you have two nested steps with some "normal" code between them, it would need to be able to read the step declarations, and then invoke over it (if that makes sense?).
I know that Delay and Run are things that are used in deferred execution for computation expressions, but I'm not sure if they help me here, as they unfortunately evaluate for everything.
I'm most likely missing something glaringly obvious and very "functional-y" but I just can't seem to make it do what I want.
Clarification
I'm using id for demonstration, they're part of the puzzle, and I imagine how I might surface the "invokable" parts of my expression that I want.
As mentioned in the other answer, free monads provide a useful theoretical framework for thinking about this kind of problems - however, I think you do not necessarily need them to get an answer to the specific question you are asking here.
First, I had to add Return to your computation builder to make your code compile. As you never return anything, I just added an overload taking unit which is equivalent to Zero:
member this.Return( () ) = this.Zero()
Now, to answer your question - I think you need to modify your discriminated union to allow delaying of computations that produce Entry - you do have functions unit -> unit in the domain model, but that's not quite enough to delay a computation that will produce a new entry. So, I think you need to extend the type:
type Entry =
| Leaf of string * (unit -> unit)
| Node of string * Entry list * (unit -> unit)
| Delayed of (unit -> Entry)
When you are evaluating Entry, you will now need to handle the Delayed case - which contains a function that might perform side-effect such as printing "TEST".
Now you can add Delay to your computation builder and also implement the missing case for Delayed in Bind like this:
member this.Delay(f) = Delayed(f)
member this.Bind(v:Entry, f:unit->Entry) = Delayed(fun () ->
let rec loop = function
| Delayed f -> loop (f())
| Node(label,children,a) -> Node(label, v :: children, a)
| Leaf(label,a) -> Node(label, [v], a)
loop (f()) )
Essentially, Bind will create a new delayed computation that, when called, evaluates the entry v until it finds a node or a leaf (collapsing all other delayed nodes) and then does the same thing as what your code did before.
I think this answers your question - but I'd be a bit careful here. I think computation expressions are useful as a syntactic sugar, but they are very harmful if you think about them more than you think about the domain of the problem that you are actually solving - in the question, you did not say much about your actual problem. If you did, the answer might be very different.
You wrote:
Ideally what I want is to be able to retrieve the tree structure, but be able to call some returned function/s that will then execute the inner blocks.
This is an almost perfect description of the "free monad", which is basically the functional-programming equivalent of the OOP "interpreter pattern". The basic idea behind the free monad is that you convert imperative-style code into a two-step process. The first step builds up an AST, and the second step executes the AST. That way you can do things in between step 1 and step 2, like analyze the tree structure without executing the code. Then when you're ready, you can run your "execute" function, which takes the AST as input and actually does the steps it represents.
I'm not experienced enough with free monads to be able to write a complete tutorial on them, nor to directly answer your question with a step-by-step specific free-monad solution. But I can point you to a few resources that may help you understand the concepts behind them. First, the required Scott Wlaschin link:
https://fsharpforfunandprofit.com/posts/13-ways-of-looking-at-a-turtle-2/#way13
This is the last part of his "13 ways of looking at a turtle" series, where he builds a small LOGO-like turtle-graphics app using many different design styles. In #13, he uses the free-monad style, building it from the ground up so you can see the design decisions that go into that style.
Second, a set of links to Mark Seemann's blog. For the past month or two, Mark Seemann has been writing posts about the free-monad style, though I didn't realize that that's what he was writing about until he was several articles in. There's a terminology difference that may confuse you at first: Scott Wlaschin uses the terms "Stop" and "KeepGoing" for the two possible AST cases ("this is the end of the command list" vs. "there are more commands after this one"). But the traditional names for those two free-monad cases are "Pure" and "Free". IMHO, the names "Pure" and "Free" are too abstract, and I like Scott Wlaschin's "Stop" and "KeepGoing" names better. But I mention this so that when you see "Pure" and "Free" in Mark Seemann's posts, you'll know that it's the same concept as Scott Wlaschin's turtle example.
Okay, with that explanation finished, here are the links to Mark Seemann's posts:
http://blog.ploeh.dk/2017/06/27/pure-times/
http://blog.ploeh.dk/2017/06/28/pure-times-in-haskell/
http://blog.ploeh.dk/2017/07/04/pure-times-in-f/
http://blog.ploeh.dk/2017/07/10/pure-interactions/
http://blog.ploeh.dk/2017/07/11/hello-pure-command-line-interaction/
http://blog.ploeh.dk/2017/07/17/a-pure-command-line-wizard/
http://blog.ploeh.dk/2017/07/24/combining-free-monads-in-haskell/
http://blog.ploeh.dk/2017/07/31/combining-free-monads-in-f/
http://blog.ploeh.dk/2017/08/07/f-free-monad-recipe/
Mark intersperses Haskell examples with F# examples, as you can tell from the URLs. If you are completely unfamiliar with Haskell, you can probably skip those posts as they might confuse you more than they help. But if you've got a passing familiarity with Haskell syntax, seeing the same ideas expressed in both Haskell and F# may help you grasp the concepts better, so I've included the Haskell posts as well as the F# posts.
As I said, I'm not quite familiar enough with free monads to be able to give you a specific answer to your question. But hopefully these links will give you some background knowledge that can help you implement what you're looking for.

What to keep in mind while learning F#, having learned Scheme

I'm quite interested in learning F#.
My only experience with functional languages has been 2 introductory courses on Scheme in college.
Are there any things that I should keep in mind while learning F#, having previously learned Scheme? Any differences in methodologies, gotchas or other things that might give me trouble?
Are there any things that I should keep in mind while learning F#, having previously learned Scheme? Any differences in methodologies, gotchas or other things that might give me trouble?
Static typing is the major difference between Scheme and F#. This facilitates a style called typeful programming where the type system is used to encode constraints about functions and data such that the compiler proves these aspects of the program correct at compile time and any violations of the constraints are caught immediately.
For example, a sequence of one or more elements of the same type might be conveyed by a value of the following type:
type list1<'a> = List1 of 'a * 'a list
let xs = List1(1, [])
let ys = List1(2, [3; 4])
The compiler now guarantees that any attempt to use an empty one of these sequences will be caught at compile time as an error.
Now, the reduce function makes no sense on an empty sequence so the built-in implementation for lists barfs at run-time with an exception if it encounters an empty sequence:
> List.reduce (+) [];;
System.ArgumentException: The input list was empty.
Parameter name: list
at Microsoft.FSharp.Collections.ListModule.Reduce[T](FSharpFunc`2 reduction, FSharpList`1 list)
at <StartupCode$FSI_0271>.$FSI_0271.main#()
Stopped due to error
With our new sequence of one or more elements, we can now write a reduce function that never barfs at run-time with an exception because its input is guaranteed by the type system to be non-empty:
let rec reduce f = function
| List1(x, []) -> x
| List1(x0, x1::xs) -> f x0 (reduce f (List1(x1, xs)))
This is a great way to improve the reliability of software by eliminating sources of run-time errors and it is something that dynamically typed languages like Scheme cannot even begin to do.
Scheme is a nice functional language; learning it in school should provide a good foundation for functional programming.
F# is statically-typed whereas Scheme is dynamic, so that is one obvious difference. If you have experience with other static languages (especially .NET languages like C#) then that will not be a big deal, but if most of your experience is dynamic, that will be a change.
Learning the names of the main F# functional programming functions (things like List.map) is important; most every functional language has the same basic set but often with different names (I don't recall the main Scheme names to compare).
If you have old Scheme 'programming assignments' with sample inputs/outputs handy, it may be useful to re-code them in F# as a way to 'warm up' with the language.
I suggest considering Haskell too, and they are roughly in the same family as F# and ML, and Haskell contains a lot of interesting functional concepts not found elsewhere.
Take a look at tryhaskell.org for an interactive online tutorial.

F#: is it OK for developing theorem provers?

Please advise. I am a lawyer, I work in the field of Law Informatics. I have been a programmer for a long time (Basic, RPG, Fortran, Pascal, Cobol, VB.NET, C#). I am currently interested in F#, but I'd like some advise. My concern is F# seems to be fit for math applications. And what I want would require a lot of Boolean Math operations and Natural Language Processing of text and, if successful, speech. I am worried about the text processing.
I received a revolutionary PROLOG source code (revolutionary in the field of Law and in particular Dispute Resolution). The program solves disputes by evaluating Yes-No (true-false) arguments advanced by two debating parties. Now, I am learning PROLOG so I can take the program to another level: evaluate the strenght of arguments when they are neither a Yes or No, but a persuasive element in the argumentation process.
So, the program handles the dialectics aspect of argumentation, I want it to begin processing the rhetoric aspect of argumentation, or at least some aspects.
Currently the program can manage formal logic. What I want is to begin managing some aspects of informal logic and for that I would need to do parsing of strings (long strings, maybe ms word documents) for the detection of text markers, words like "but" "therefore" "however" "since" etc, etc, just a long list of words I have to look up in any speech (verbal or written) and mark, and then evaluate left side and right side of the mark. Depending on the mark the sides are deemed strong or weak.
Initially, I thought of porting the Prolog program to C# and use a Prolog library. Then, it ocurred to me maybe it could be better in pure F#.
First, the project you describe sounds (and I believe this is the correct legal term) totally freaking awesome.
Second, while F# is a good choice for math applications, its also extremely well-suited for any applications which perform a lot of symbolic processing. Its worth noting that F# is part of the ML family of languages which were originally designed for the specific purpose of developing theorem provers. It sounds like you're writing an application which appeals directly to the niche ML languages are geared for.
I would personally recommend writing any theorem proving applications you have in F# rather than C# -- only because the resulting F# code will be about 1/10th the size of the C# equivalent. I posted this sample demonstrating how to evaluate propositional logic in C# and F#, you can see the difference for yourself.
F# has many features that make this type of logic processing natural. To get a feel for what the language looks like, here is one possible way to decide which side of an argument has won, and by how much. Uses a random result for the argument, since the interesting (read "very hard to impossible") part will be parsing out the argument text and deciding how persuasive it would be to an actual human.
/// Declare a 'weight' unit-of-measure, so the compiler can do static typechecking
[<Measure>] type weight
/// Type of tokenized argument
type Argument = string
/// Type of argument reduced to side & weight
type ArgumentResult =
| Pro of float<weight>
| Con of float<weight>
| Draw
/// Convert a tokenized argument into a side & weight
/// Presently returns a random side and weight
let ParseArgument =
let rnd = System.Random()
let nextArg() = rnd.NextDouble() * 1.0<weight>
fun (line:string) ->
// The REALLY interesting code goes here!
match rnd.Next(0,3) with
| 1 -> Pro(nextArg())
| 2 -> Con(nextArg())
| _ -> Draw
/// Tally the argument scored
let Score args =
// Sum up all pro & con scores, and keep track of count for avg calculation
let totalPro, totalCon, count =
args
|> Seq.map ParseArgument
|> Seq.fold
(fun (pros, cons, count) arg ->
match arg with
| Pro(w) -> (pros+w, cons, count+1)
| Con(w) -> (pros, cons+w, count+1)
| Draw -> (pros, cons, count+1)
)
(0.0<weight>, 0.0<weight>, 0)
let fcount = float(count)
let avgPro, avgCon = totalPro/fcount, totalCon/ fcoun
let diff = avgPro - avgCon
match diff with
// consider < 1% a draw
| d when abs d < 0.01<weight> -> Draw
| d when d > 0.0<weight> -> Pro(d)
| d -> Con(-d)
let testScore = ["yes"; "no"; "yes"; "no"; "no"; "YES!"; "YES!"]
|> Score
printfn "Test score = %A" testScore
Porting from prolog to F# wont be that straight forward. While they are both non-imperative languages. Prolog is a declarative language and f# is functional. I never used C# Prolog libraries but I think it will be easier then converting the whole thing to f#.
It sounds like the functional aspects of F# are appealing to you, but you wonder if it can handle the non-functional aspects. You should know that F# has the entire .NET Framework at its disposal. It also is not a purely functional language; you can write imperative code in it if you want to.
Finally, if there are still things you want to do from C#, it is possible to call F# functions from C#, and vice versa.
While F# is certainly more suitable than C# for this kind of application since there're going to be several algorithms which F# allows you to express in a very concise and elegant way, you should consider the difference between functional, OO, and logic programming. In fact, porting from F# will most likely require you to use a solver (or implement your own) and that might take you some time to get used to. Otherwise you should consider making a library with your prolog code and access it from .NET (see more about interop at this page and remember that everything you can access from C# you can also access from F#).
F# does not support logic programming as Prolog does. you might want to check out the P# compiler.

Explaining pattern matching vs switch

I have been trying to explain the difference between switch statements and pattern matching(F#) to a couple of people but I haven't really been able to explain it well..most of the time they just look at me and say "so why don't you just use if..then..else".
How would you explain it to them?
EDIT! Thanks everyone for the great answers, I really wish I could mark multiple right answers.
Having formerly been one of "those people", I don't know that there's a succinct way to sum up why pattern-matching is such tasty goodness. It's experiential.
Back when I had just glanced at pattern-matching and thought it was a glorified switch statement, I think that I didn't have experience programming with algebraic data types (tuples and discriminated unions) and didn't quite see that pattern matching was both a control construct and a binding construct. Now that I've been programming with F#, I finally "get it". Pattern-matching's coolness is due to a confluence of features found in functional programming languages, and so it's non-trivial for the outsider-looking-in to appreciate.
I tried to sum up one aspect of why pattern-matching is useful in the second of a short two-part blog series on language and API design; check out part one and part two.
Patterns give you a small language to describe the structure of the values you want to match. The structure can be arbitrarily deep and you can bind variables to parts of the structured value.
This allows you to write things extremely succinctly. You can illustrate this with a small example, such as a derivative function for a simple type of mathematical expressions:
type expr =
| Int of int
| Var of string
| Add of expr * expr
| Mul of expr * expr;;
let rec d(f, x) =
match f with
| Var y when x=y -> Int 1
| Int _ | Var _ -> Int 0
| Add(f, g) -> Add(d(f, x), d(g, x))
| Mul(f, g) -> Add(Mul(f, d(g, x)), Mul(g, d(f, x)));;
Additionally, because pattern matching is a static construct for static types, the compiler can (i) verify that you covered all cases (ii) detect redundant branches that can never match any value (iii) provide a very efficient implementation (with jumps etc.).
Excerpt from this blog article:
Pattern matching has several advantages over switch statements and method dispatch:
Pattern matches can act upon ints,
floats, strings and other types as
well as objects.
Pattern matches can act upon several
different values simultaneously:
parallel pattern matching. Method
dispatch and switch are limited to a single
value, e.g. "this".
Patterns can be nested, allowing
dispatch over trees of arbitrary
depth. Method dispatch and switch are limited
to the non-nested case.
Or-patterns allow subpatterns to be
shared. Method dispatch only allows
sharing when methods are from
classes that happen to share a base
class. Otherwise you must manually
factor out the commonality into a
separate function (giving it a
name) and then manually insert calls
from all appropriate places to your
unnecessary function.
Pattern matching provides redundancy
checking which catches errors.
Nested and/or parallel pattern
matches are optimized for you by the
F# compiler. The OO equivalent must
be written by hand and constantly
reoptimized by hand during
development, which is prohibitively
tedious and error prone so
production-quality OO code tends to
be extremely slow in comparison.
Active patterns allow you to inject
custom dispatch semantics.
Off the top of my head:
The compiler can tell if you haven't covered all possibilities in your matches
You can use a match as an assignment
If you have a discriminated union, each match can have a different 'type'
Tuples have "," and Variants have Ctor args .. these are constructors, they create things.
Patterns are destructors, they rip them apart.
They're dual concepts.
To put this more forcefully: the notion of a tuple or variant cannot be described merely by its constructor: the destructor is required or the value you made is useless. It is these dual descriptions which define a value.
Generally we think of constructors as data, and destructors as control flow. Variant destructors are alternate branches (one of many), tuple destructors are parallel threads (all of many).
The parallelism is evident in operations like
(f * g) . (h * k) = (f . h * g . k)
if you think of control flowing through a function, tuples provide a way to split up a calculation into parallel threads of control.
Looked at this way, expressions are ways to compose tuples and variants to make complicated data structures (think of an AST).
And pattern matches are ways to compose the destructors (again, think of an AST).
Switch is the two front wheels.
Pattern-matching is the entire car.
Pattern matches in OCaml, in addition to being more expressive as mentioned in several ways that have been described above, also give some very important static guarantees. The compiler will prove for you that the case-analysis embodied by your pattern-match statement is:
exhaustive (no cases are missed)
non-redundant (no cases that can never be hit because they are pre-empted by a previous case)
sound (no patterns that are impossible given the datatype in question)
This is a really big deal. It's helpful when you're writing the program for the first time, and enormously useful when your program is evolving. Used properly, match-statements make it easier to change the types in your code reliably, because the type system points you at the broken match statements, which are a decent indicator of where you have code that needs to be fixed.
If-Else (or switch) statements are about choosing different ways to process a value (input) depending on properties of the value at hand.
Pattern matching is about defining how to process a value given its structure, (also note that single case pattern matches make sense).
Thus pattern matching is more about deconstructing values than making choices, this makes them a very convenient mechanism for defining (recursive) functions on inductive structures (recursive union types), which explains why they are so abundantly used in languages like Ocaml etc.
PS: You might know the pattern-match and If-Else "patterns" from their ad-hoc use in math;
"if x has property A then y else z" (If-Else)
"some term in p1..pn where .... is the prime decomposition of x.." ((single case) pattern match)
Perhaps you could draw an analogy with strings and regular expressions? You describe what you are looking for, and let the compiler figure out how for itself. It makes your code much simpler and clearer.
As an aside: I find that the most useful thing about pattern matching is that it encourages good habits. I deal with the corner cases first, and it's easy to check that I've covered every case.

Resources