On commence par écrire la fonction qui fait le produit de deux monômes.
let produit_monomes {coeff=c1 ; degre=d1} {coeff=c2 ; degre=d2} = {coeff=c1*c2 ; degre=d1+d2}
En remarquant que le produit de deux monômes non-nuls est encore un monôme non-nul, on peut simplement itérer une fois avec List.map et obtenir le polynôme produit d'un monôme non-nul et d'un polynôme.
let produit_monome m p = List.map (fun mp -> produit_monome m mp) p
Voire même en utilisant l'application partielle :
let produit_monome m p = List.map (produit_monomes m) p
En effet comme l'indique son type monome -> monome -> monome, à lire comme monome -> (monome -> monome), la fonction produit_monomes prend un monôme en argument et renvoie une fonction qui prend un monome en argument.

On termine en itérant encore une fois, à l'aide cette fois de List.fold_right.

let produit p1 p2 = List.fold_right (fun m1 r -> somme (produit_monome m1 p2) r) p1 []

On peut noter que, puisque somme est associative, on peut ici aussi employer List.fold_left.

let produit p1 p2 = List.fold_left (fun r m1 -> somme (produit_monome m1 p2) r) [] p1

Culture

Alors fold_right ou fold_left ? En ce qui me concerne, je me contente de me souvenir que l'on peut facilement copier une liste avec fold_right et facilement inverser une liste avec fold_left.

let copy l = List.fold_right (fun x r -> x::r) l [] let rev l = List.fold_left (fun r x -> x::r) [] l

Il y a une certaine logique dans tout ça et notamment dans l'ordre des arguments. En cas de choix possible (opération associative et commutative par exemple), la préférence va plutôt à fold_left qui est récursive terminale.