In case you came to this question and thought you had finally reached that moment when you find exactly what you're looking for, then were disappointed, here is a more explicit answer:
You cannot use %prec for the reason Thelema mentioned. So you must define associativity in establishing a recursive set of rules.
Here's a simplified parser.mly
%token <int> Num
%token <string> Id
%token TRUE FALSE
%token LET REC EQ IN FUN ARROW IF THEN ELSE
%token PLUS MINUS MUL DIV LT LE NE AND OR
%token EOF
%start exp
%type <Simple.expr> exp
%%
/* Associativity increases from exp to exp8
* Each precedence level trickles down to higher level expressions if the pattern is not matched
*/
/* Parses the LET, LET REC, FUN, and IF expressions */
exp:
LET Id EQ exp IN exp { Let($2,$4,$6) }
| LET REC Id EQ exp IN exp { Letrec($3,$5,$7) }
| FUN Id ARROW exp { Fun($2,$4) }
| IF exp THEN exp ELSE exp { If($2,$4,$6) }
| exp2 { $1 }
/* Parses OR expressions */
exp2:
exp2 OR exp3 { Bin($1,Or,$3) }
| exp3 { $1 }
/* Parses AND expressions */
exp3:
exp3 AND exp4 { Bin($1,And,$3) }
| exp4 { $1 }
/* Parses EQ, NE, LT, and LE expressions */
exp4:
exp4 EQ exp5 { Bin($1,Eq,$3) }
| exp4 NE exp5 { Bin($1,Ne,$3) }
| exp4 LT exp5 { Bin($1,Lt,$3) }
| exp4 LE exp5 { Bin($1,Le,$3) }
| exp5 { $1 }
/* Parses PLUS and MINUS expressions */
exp5:
exp5 PLUS exp6 { Bin($1,Plus,$3) }
| exp5 MINUS exp6 { Bin($1,Minus,$3) }
| exp6 { $1 }
/* Parses MUL and DIV expressions */
exp6:
exp6 MUL exp7 { Bin($1,Mul,$3)}
| exp6 DIV exp7 { Bin($1,Div,$3)}
| exp7 { $1 }
/* Parses Function Application expressions */
exp7:
exp7 exp8 { Apply($1,$2) }
| exp8 { $1 }
/* Parses numbers (Num), strings (Id), booleans (True | False), and expressions in parentheses */
exp8:
Num { Const($1) }
| Id { Var($1) }
| TRUE { True }
| FALSE { False }
| LPAREN exp RPAREN { $2 }
The recursive work-around is really for catching the case that we're concerned with regarding this question, however it's easy to see how it can be applied to defining associativity for the rest of the expressions as well.
The gist of this approach is to attempt to match the pattern in question against the patterns defined in the start case (exp), and you leave a call to the immediately succeeding case (exp2) as a catch-all pattern if your pattern does not match any before it; continuing with this approach until the pattern finally matches. This implies that the highest priority pattern exists in the furthest down case - in this example, exp8.
In this example, the case for Apply (Function Application) is in exp7. This is because Apply is defined to have the highest associativity of any pattern in this example. The reason it does not have priority over the cases in exp8 is due to Apply evaluating to further calls to expression cases, not value calls. If exp8 didn't exist, we'd have an infinite look on our hands.
In the hypothetical simple.ml, Function Application is defined as an expression of the following property: Apply of expr * expr. And since Apply is left-recursive, we are evaluating the right expression (exp8) and recursing on the left (exp7).