Note that the "cross apply" mechanism from SQL Server would solve this, but it isn't available in PostgreSQL. Basically, it was their solution for how to pass parameters (which tend to be references to columns external to the current table expression) to functions called as table expressions in the FROM clause. But it turned out to be useful for all kinds of situations where you want to avoid another level of subquery nesting or moving things from the FROM clause to the SELECT clause. PostgreSQL made it possible to do this by making kind of an exception -- you can pass parameters like that if the expression is a simple function call but not strictly speaking an embedded SELECT. So
left join highestPriorityProductSpecial(p.id) on true
is ok, but not
left join (select * from product_special ps where ps.id = p.id order by priority desc limit 1) on true
even though the definition of the function is precisely that.
So, that is in fact a handy solution (in 9.1 at least): make a function to extract your highest priority row by doing the limit inside the function.
But functions have the drawback that the query plan will not show what is going on inside them and I believe it will always choose a nested loop join, even when that might not be best.