pattern matching à la ocaml/haskell
dionysos9-Oct-2011/17:35:31+2:00
Bonjour,
Connaissez vous le langage OCaml ou Haskell?
Cela permet des pattern matching du type
let imply v = match v with 
     (true,true)   -> true
   | (true,false)  -> false
   | (false,true)  -> true
   | (false,false) -> true;;

let imply v = match v with 
     (true,x)  -> x
   | (false,x) -> true;;

let equal c = match c with 
    (x,x) -> true
  | (x,y) -> false;;

let is_a_vowel c = match c with 
    'a' | 'e' | 'i' | 'o' | 'u' | 'y' -> true 
  | _ -> false ;;

let rec fib = function
    (0 | 1) as i -> i
  | i -> fib (i - 1) + fib (i - 2);;


Aujourd'hui, je suis tombé sur ce site http://www.brool.com/index.php/pattern-matching-in-clojure qui propose une macro pour ajouter cette fonctionnalité au langage Clojure. Donc je me suis dis pourquoi l'ajouter à rebol.
Le code de la macro est fourni, mais comme je regarde Clojure que depuis très peu de temps, je ne comprends pas tout ce code (quote, unquote etc...)

D'après vous, quelle serait la meilleure solution pour implémenter cette fonctionnalité? parse? la possibilité de faire set [one two three] [1 2 3 4]? (c'est bien ce que l'on appelle du destructuring?)

J'ai commencé à faire une implémentation à l'aide de parse...
dionysos9-Oct-2011/17:37:32+2:00
Donc je me suis dis pourquoi pas l'ajouter à rebol en guise d'exercice
Didec10-Oct-2011/14:26:15+2:00
Ne connaissant pas ces langages, il manque pour mois des exemples d'utilisation des ces définitions.
Pourrais-tu compléter avec ces exemples que l'on se fasse une idée globale du truc ?
none11-Oct-2011/20:48:21+2:00
Le pattern matching défini dans le langage OCaml ou Haskell n'a rien à voir avec celui de Python, Perl ou Ruby. Le pattern matching de Python, Perl ou Ruby se limite au string (expression rationnelle). Le pattern matching des langages fonctionnel OCaml/Haskell est réellement beaucoup plus puissant. On peut le voir comme une généralisation. Il est fonctionnel et symbolique. D'après wikipedia, le pattern matching permet de déconstruire une valeur (tableau, liste, structure de données etc...) selon leur spécification de type. (A savoir que les langages OCaml et Haskell sont des langages compilés et fortement typés au contraire de Rebol, Lisp, Scheme)

Après ce rapide aperçu théorique, voici quelques exemples d'utilisation de ce que je veux obtenir en Rebol*.
Dans ce qui suit, la fonction match permet de réaliser le pattern matching.
Le prototype est le suivant
match: func [block body]


; l'exemple est tiré de http://www.brool.com/index.php/pattern-matching-in-clojure et est très orienté lisp
compute: func [expr /local a b][ ; TODO: devrait forcer toute les vr en locales
	match expr [
		[a] [a]
		["add" a b] [(compute a) + (compute b)]
		["sub" a b] [(compute a) - (compute b)]
		["mul" a b] [(compute a) * (compute b)]
		["div" a b] [(compute a) / (compute b)]
	]
]
compute [3]
>> 3

compute ["add" 5 4]
>> 9

compute ["sub" ["add" ["mul" 6 7] 8] 5]
>> 45



imply: func [expr][
	match expr [
		[true x] [x]
		[false x] [true]
	]
]

imply [true false]
>> false
imply [true true]
>> true
imply [false false]
>> true
imply [false true]
>> true
imply [true 4]
>> 4

ident: func [expr][
	match expr [
		[x] ["one element"]
		[x x] ["identical"] ;;FIXME: si cette regle est placée apres la regle [x y] ca ne fonctionne plus!!!! 
		[x y] ["two different element"]
		[] ["empty"]
	]
]

ident [1 1]
>> "identical"
ident ["1" "1"]
>> "identical"
ident [1 2]
>> "two different element"
ident [0]
>> "one element"

factorial: func [expr][
	match expr [
		[0] [1]
		[x] [x * factorial x - 1]
	]
]


Ce qu'il faut comprendre c'est que le pattern matching est symbolique.
Le pattern [x x] signifie qu'on filtre sur un bloque qui contient 2 éléments identiques (car on a le même symbole x (word dans la terminologie rebol).
Le pattern [x y] signifie qu'on filtre sur un bloque qui contient 2 éléments différents (car le filtre contient de symbole différent)

Si on utilise le pattern [x y x], les expressions suivantes seront validées
[1 0 1]
[2 5 2]
['a 'b 'a]
["test" 3 "test"]
[[1 2 3] [4 5] [1 2 3]]

Dans ocaml (je ne connais pas haskell), il y a des règles comme [ _ x x]. '_' indique que ça match n'importe quoi.

Imaginons que tu veuilles fournir une fonction qui te calcule la dérivée d'une fonction.
Le pattern matching va reposer les règles de base pour la dérivation d'une fonction f(x):.
- la dérivée de x -> 1
- la dérivée de cste -> 0
- la dérivée de (f + g)' -> f' + g'
- la dérivée de (f . g)' -> f' . g + f . g'

Et voici ce que cela donnerait en ocaml:
# 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)));;
val d : expr * string -> expr = <fun>
For example, the derivative of ax 2 +bx+c is:

# let a, b, c, x = Var "a", Var "b", Var "c", Var "x";;
val a : expr = Var "a"
val b : expr = Var "b"
val c : expr = Var "c"
val x : expr = Var "x"
# let f = Add(Add(Mul(Mul(a, x), x), Mul(b, x)), c);;
val f : expr =
  Add (Add (Mul (Mul (Var "a", Var "x"), Var "x"), Mul (Var "b", Var "x")),
   Var "c")
# d(f, "x");;
- : expr =
  Add
   (Add
     (Add (Mul (Mul (Var "a", Var "x"), Int 1),
       Mul (Var "x", Add (Mul (Var "a", Int 1), Mul (Var "x", Int 0)))),
     Add (Mul (Var "b", Int 1), Mul (Var "x", Int 0))),
   Int 0)

élégant comme définition?

Si j'ai choisi cette exemple, c'est parce que c'est un exemple qui m'avait fait comprendre la puissance du pattern matching ocaml. Je ne suis pas du tout théoricien en math ou info

J'espère que tout ça est plus clair.

J'ai pas mal avancé, mais je bute sur des "limitations" rebol que j'essaie de contourner.
J'ai tenté la solution parse en premier => échec (je n'arrive pas appeler recursivement la fonction compute de mon 1er exemple. Je pense qu'avec un binding ca devrait fonctionner mais je ne connais pas trop).
Je suis maintenant sur une implémentation basée sur le destructuring (set [one two three] [1 2 3 4])
gerardcote11-Oct-2011/20:50:46+2:00
J'ai déjà demandé à Maxim ce qu'il en pensait et il m'a répondu que c'était tout faisable via Parse en REBOL mais j'aurais moi aussi aimé simplifier en faisant un petit dialecte qui aurait de lui-même généré le code du parse pour des expressions simples.

Depuis j'ai trouvé comment faire du pattern matching en LOGO de toute pièce mais pas encore trouvé l'étincelle me permettant de trouver plus de temps pour expérimenter avec REBOL ces concepts - mais cela viendra. Pour le moment, je me penche sur l'original de tous les langages de haut niveau: Common Lisp. Wow! Cela me permettra enfin de mieux apprécier REBOL et de mieux en comprendre les comportements à l'interne. Il me manquait pas mal de concepts de haute voltige - j'en suis maintenant conscient...

http://xahlee.org/ocaml/pattern_matching.html

Entretemps, pour ton info Didec, voici un court extrait d'un exemple de OCaml qui me semble être plus significatif de ce que tu recherches.

Je peux aussi vous refiler un lien vers les sources du pattern matching tel décrit et réalisé en LOGO par l'auteur du Berkeley Logo, Brian Harvey (ses 3 tomes sur la programmation pour non programmeurs sont un vrai délice et en voici le lien vers les PDF.)

http://www.cs.berkeley.edu/~bh/logo.html

C'est ici (chap 7 du volume 2 que son pattern matcher est décrit : http://www.cs.berkeley.edu/~bh/pdf/v2ch07.pdf)


A+
Gérard
dionysos11-Oct-2011/21:17:15+2:00
@Gérard :"J'ai déjà demandé à Maxim ce qu'il en pensait et il m'a répondu que c'était tout faisable via Parse en REBOL mais j'aurais moi aussi aimé simplifier en faisant un petit dialecte qui aurait de lui-même généré le code du parse pour des expressions simples."

C'est ce que j'ai fait (générer automatiquement des règles parse.

pattern-to-rule: func [block pattern action /local rule] [
	rule: copy []
	if (length? block) = (length? pattern) [
		forall block [
			index: index? block
			either (type? first block) = string! [append rule reduce [pattern/:index]] 
			[append rule reduce ['set pattern/:index type? first block]]
		]
		append rule reduce [to-paren action]
	]
	;probe rule
	rule
]

;version où match appelle parse avec une seule règle qui contient l'ensemble des patterns
match: func [block body /local rule res][
	rule: copy []
	foreach [pattern action] body [
		append rule pattern-to-rule block pattern append append copy [] [res:] action
		append rule '|
	]
	remove back tail rule
	print [">" mold block "match with" mold rule]
	probe parse block rule
	probe res
	return res
]
;version où match parse chaque pattern indivuellement
match: func [block body /local rule res ok?][
	rule: copy []
	block: to-block block
	foreach [pattern action] body [
		rule: pattern-to-rule block pattern append append copy [] [res:] action
		print [">" mold block "match with" mold rule]
		probe parse block rule
		;if ok? [print [">" mold block "match with" mold rule]]
	]
	return res
]
jocko11-Oct-2011/21:26:49+2:00
dionysos, peux tu donner un ou deux exemples d'utilisation ?
none11-Oct-2011/21:47:13+2:00
@Gérard : Merci pour les liens, il y a des choses très intéressantes dans Volume 3: Beyond Programming sur la conception et l'implémentation de langage. Dommage que je connaisse pas Logo, les exemples sont plus dure à comprendre.

@jocko
probe pattern-to-rule ["add" 5 4] [a] [a]
>> []
probe pattern-to-rule ["add" 5 4] ["add" a b] [print a + b]
>> ["add" set a integer! set b integer! (print a + b)]
probe pattern-to-rule["add" ["add" 1 1] 4] ["add" a b] [print a + b]
>> ["add" set a block! set b integer! (print a + b)]
probe pattern-to-rule [4] [a] [a]
>> [set a integer! (a)]

La fonction compute donné en exemple dans le message 11-Oct-2011/20:48 te montrera un cas d'utilisation.
Comme je le disais j'ai abandonné l'implémentation basée sur parse.
Peut-être devrais je reprendre.
Tous les tests fait avec la fonction compute fonctionne.
Par contre mes tests sont KO avec les fonction imply et ident.
dionysos13-Oct-2011/21:10+2:00
Bonsoir,
j'ai repris l'implémentation à base de parse.
Je pense que cela ne peut pas fonctionner sur un matching du type:

ident: func [expr][
	match expr [
		[x] ["one element"]
		[x x] ["identical"] 
		[x y] ["two different element"]
		[] ["empty"]
	]
]
ident [1 2]


l'aglo va générer les règle
1) [x] ["one element"] => KO
2) [x x] ["identical"] => parse [1 2] [set x integer! set x integer! (res: "identical")]
3) [x y] ["two different element"] => parse [1 2] [set x integer! set y integer! (res: "two different element")]
4) [] ["empty"] => KO

L'algo traite séquentiellement les 4 règles et s'arrête à la 1ere règle qui match.
Ici, c'est la règle 2 qui match ce qui n'est pas correct!

Il faudrait que la règle parse reflète le sens de [x x], c'est à dire un bloc qui contient 2 éléments (peu importe leurs définitions) identiques.

Avant de réaliser le 2ième "set x integer!" il faudrait vérifier si x est déjà affecté à une valeur et si oui vérifer si la nouvelle valeur à réaffecter est identique.

Une idée? Une proposition?
shadwolf14-Oct-2011/1:47:40+2:00
20/20 a cette discution ... bon ca va paraitre nebuleux a ceux qui connaissent pas rebol et pas parse.

mais rien a dire de plus: Qu'enfin une vraie discution sur comment faire des choses avec rebol.
dionysos25-Oct-2011/21:34:40+2:00
J'ai abandonné l'implémentation à base de parse pour partir sur un algorithme d'unification (merci google).
L'unification est une généralisation de ce que je cherchais à faire.
Voir wikipedia pour plus d'info (unification substitution).
En faisant des recherches, je suis tombé sur la librairie JUnify en Javascript.
Je l'ai adapté à Rebol. Ce qui est marrant, c'est que cette implémentation ressemble à celle que j'avais mis en place avec le destructuring.
La différence, c'est que j'ai pu voir comment le problème sur lequel je butais, a été résolu dans JUnify.
J'ai dû faire pas mal de modifications. Et maintenant ça fonctionne parfaitement. Tout mes tests (qui ressemblent aux cas d'utilisation montrés plus haut) fonctionnent.
Je dois compléter les tests, améliorer un peu le code, voilà pour les news!
shadwolf27-Oct-2011/0:26:37+2:00
hum mais maintenant que tu maitrise mieux le sujet reviendras tu as une implementation a base de parse ?

oui bien ca n'a aucun interet (enfin moi je vois bien un interet mais il est purement didactique...)
coccinelle29-Oct-2011/10:04:04+2:00
Si cela t'intéresse, j'avais traité l'unification en Rebol pour une implémentation de Prolog en Rebol : http://www.rebol.org/view-script.r?script=prolog.r

Tu peux peut-être y trouver des idées intéressantes. De mémoire, j'avais essayé de faire une implémentation à base de parse mais sans grand succès. J'avais aussi utilisé une approche récursive finalement assez pénalisante en matière de performance. Au final, c'est une simple boucle qui traite la chose.
dionysos3-Nov-2011/21:14:33+1:00
@shadwolf
oui peut être que j'essaierais une implémentation à base de parse. Pour l'instant, pas le temps. J'ai quand même trouvé une solution à mon problème du 13/10, mais ça s'apparente à de la bidouille qui me plait pas trop.

@coccinelle
En faisant des recherche sur les algo d'unification, je suis tombé sur Prolog. Quand j'ai découvert Rebol, il y a quelques mois, en parcourant altme, j'étais tombé sur ton implémentation de Prolog. Malheureusement j'ai pas fait le lien. je regarderais ça.
Sinon je ne connais pas le langage Prolog, je suppose que ça va bien au dela du pattern matching que je voulais implémenter.
A l'occasion, aurais tu un exemple d'utilisation de ta librairie Prolog pour faire du pattern matching comme ceux plus présentés plus haut?
guest24-Nov-2011/2:34+1:00
j'avoue que j'ai un peu de mal à voir l'intérêt de la démarche.
Le pattern matching c'est du parsing et du code associé.
Exactement ce que sait faire parse nativement.

A part proposer une syntaxe différente de celle de parse y'a aucun avantage.
coccinelle4-Nov-2011/8:10:17+1:00
Prolog va un peu au delà de ce que tu veux faire mais c'est facile de le faire en Prolog. Jette un coup d'oeil sur les exemples que j'ai donné et tu comprendr assez vite : http://www.rebol.org/documentation.r?script=prolog.r#sect2

Login required to Post.


Powered by RebelBB and REBOL 2.7.8.4.2