Polymorphisme ad-hoc

Le problème exposé dans ce sujet a été résolu.

Salut,

Je cherche à créer une fonction qui traite un argument différemment selon son type. Pour me faire comprendre, voici un exemple en C de ce que je veux faire:

1
2
3
4
5
6
7
int arrondir(float n) {
     return (int)a;
}

int arrondir(int n) {
    return n;
}

C'est un exemple un peu moisi (je n'ai pas encore appris le C) mais ça traduis ce que je veux faire en Haskell. Comme on ne peut pas déclarer plusieurs signatures d'une même fonction, j'ai tenté un sorte de filtrage par motif avec case ... of:

1
2
3
4
-- Le traitement n'est pas important, c'est surtout le filtrage par motif qui me pose problème
maze ! position = case position of
                      Position   -> ((cells maze) !! (y position)) !! (x position)
                      (Int, Int) -> ((cells maze) !! (snd position)) !! (fst position)

Peut être que je ne devrais autoriser qu'un type pour obtenir un élément de ma matrice ? De toute façon j'aimerais savoir comment on fait, même si ce n'est pas approprié dans cette situation, ça pourrait m'aider dans un autre cas.

Merci d'avance,

AZ.

+0 -0

J'aurais dû être plus clair dans l'autre discussion : ça n'est pas ça le polymorphisme paramétrique (edit : le titre a été édité). Le polymorphisme paramétrique, c'est quand tu traites toutes les données reçues en argument de façon uniforme (par exemple parce que tu transformes une structure de données en une autre, indifféremment du type de tes arguments). La forme de polymorphisme ad-hoc que tu veux mettre en place, la surcharge, n'est possible qu'à travers les typeclasses.

(Si les discussions sur le polymorphisme t'intéressent, tu pourrais regarder l'article de Luca Cardelli dont j'ai oublié le nom. Pour être franc, moi je m'y perds un petit peu.)

Ici tu pourrais exiger que le deuxième argument de (!) soit instance d'une typeclasse particulière (c'est sans doute le cas avec l'opérateur (!) original que je t'ai montré dans l'autre discussion !) et définir deux instances, une pour le type Position, une pour le type (Int, Int). Dans le code de !, tu n'as ensuite plus qu'à utiliser les noms génériques donnés par ta typeclasse.

+0 -0

Comme je l'ai dit dans le précédent sujet, les typeclass sont encore un peu obscures pour moi, mais je crois comprendre dans ce cas. Voici donc ce que j'ai:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{-# LANGUAGE FlexibleInstances #-}

class MazePoint2D a where
    (!) :: Maze -> a -> MazeCell

instance MazePoint2D Position where
    maze ! position = ((cells maze) !! (y position)) !! (x position)

instance MazePoint2D (Int, Int) where
    maze ! position = ((cells maze) !! (snd position)) !! (fst position)

J'ai intégré l'extension FlexibleInstances sans quoi le compilateur me gueule dessus :P

Je crois avoir compris de quoi il s'agit avec quelques recherches sur le net.

Lorsque je fais maze ! Position { x = 2, y = 1 }, GHCi me ressort bien MazeCell {position = Position {x = 2, y = 1}, filled = True, intact = True} mais pas quand j'utilise les tuple (maze ! (2, 1)). Voici le message d'erreur:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<interactive>:4:6:
    No instance for (Maze.MazePoint2D (t0, t1))
      arising from a use of `!'
    The type variables `t0', `t1' are ambiguous
    Note: there is a potential instance available:
      instance Maze.MazePoint2D (Int, Int) -- Defined at Maze.hs:24:10
    In the expression: maze ! (2, 1)
    In an equation for `it': it = maze ! (2, 1)

<interactive>:4:9:
    No instance for (Num t0) arising from the literal `2'
    The type variable `t0' is ambiguous
    Note: there are several potential instances:
      instance Num Double -- Defined in `GHC.Float'
      instance Num Float -- Defined in `GHC.Float'
      instance Integral a => Num (GHC.Real.Ratio a)
        -- Defined in `GHC.Real'
      ...plus three others
    In the expression: 2
    In the second argument of `(!)', namely `(2, 1)'
    In the expression: maze ! (2, 1)

<interactive>:4:12:
    No instance for (Num t1) arising from the literal `1'
    The type variable `t1' is ambiguous
    Note: there are several potential instances:
      instance Num Double -- Defined in `GHC.Float'
      instance Num Float -- Defined in `GHC.Float'
      instance Integral a => Num (GHC.Real.Ratio a)
        -- Defined in `GHC.Real'
      ...plus three others
    In the expression: 1
    In the second argument of `(!)', namely `(2, 1)'
    In the expression: maze ! (2, 1)

Encore merci katana :)

Aehm, en fait si je déchiffre bien l’erreur, le type de ton tuple est (Num t, Num t1) => (t, t1). Comme toi t’as instancié précisément (Int, Int), ben le type du tuple n’est pas assez précis(é) pour correspondre… A priori, il faudrait donc peut-être voir le reste de ton code, et surtout les annotations de types sur tes fonctions…

Edit : Ah, FlexibleInstance règle donc le problème ?

+0 -0

J'ai intégré l'extension FlexibleInstances sans quoi le compilateur me gueule dessus :P

Est-ce que tu as cherché à comprendre pourquoi il te gueulait dessus et pourquoi intégrer cette extension était une meilleure idée que résoudre le problème sans ?

Salut Zéphyr,

Bien entendu, mais je n'ai pas bien compris:

1
2
3
4
5
6
Illegal instance declaration for `MazePoint2D (Int, Int)'
  (All instance types must be of the form (T a1 ... an)
   where a1 ... an are *distinct type variables*,
   and each type variable appears at most once in the instance head.
   Use FlexibleInstances if you want to disable this.)
In the instance declaration for `MazePoint2D (Int, Int)'

Je comprends de ce message d'erreur que les instances doivent s'appliquer sur des types dont les composantes sont de types différents, donc (Int, Int) sera invalide mais (Int, [Char]) sera correct. Cependant si je remplace à la ligne de mon erreur (Int, [Char]) j'obtiens la même erreur me demandant d'ajouter l'extension FlexibleIntstances si je veux débloquer ce comportement.

FlexibleInstance règle donc le problème ?

tanguy

Il empêche le compilateur de sortir une erreur, mais une autre erreur (celle de ce message) est levée lorsque j'appelle maze ! (2, 1). Comme tu l'a dit les types ne correspondent pas mais que mettre à la place de (Int, Int) pour que ça fonctionne ?

Merci encore à vous tous :)

+0 -0

Il empêche le compilateur de sortir une erreur, mais une autre erreur (celle de ce message) est levée lorsque j'appelle maze ! (2, 1). Comme tu l'a dit les types ne correspondent pas mais que mettre à la place de (Int, Int) pour que ça fonctionne ?

Merci encore à vous tous :)

AlphaZeta

Pour ce problème, essaie un truc du genre maze ! ((2, 1) :: (Int,Int)).

Salut,

[…] Comme tu l'a dit les types ne correspondent pas mais que mettre à la place de (Int, Int) pour que ça fonctionne ?

Ca fonctionne si tu indiques explicitement le type attendu pour forcer ton tuple à prendre le type (Int,Int) et non
(Num t, Num t1) => (t, t1) comme on te l'a fait remarquer.

Par contre est-il nécessaire de créer un nouveau type Position? Tu pourrais te contenter de faire type Position = (Int, Int).

Pour le cas de FlexibleInstances c'est une extension safe à utiliser qui facilite bien les choses donc y'a pas vraiment de soucis à ce niveau.

De manière générale, as-tu vraiment besoin de pouvoir accéder à une cellule particulière? L'utilisation de (!!) est "mal vu" dans le sens où il n'y aucune vérification sur les bornes. Tes structures de données sont-elles adaptées par rapport à ce que tu veux faire (ton algorithme)?

Salut olzd,

Par contre est-il nécessaire de créer un nouveau type Position? Tu pourrais te contenter de faire type Position = (Int, Int).

Pas faux, je n'ai pas encore tout à fait compris les types, pour instancier (au sens OO commun) il faut faire Position (x, y) c'est ça ? C'est déjà plus court que le bazar avec Position { ... }

De manière générale, as-tu vraiment besoin de pouvoir accéder à une cellule particulière? L'utilisation de (!!) est "mal vu" dans le sens où il n'y aucune vérification sur les bornes. Tes structures de données sont-elles adaptées par rapport à ce que tu veux faire (ton algorithme)?

Dans l'algorithme que j'ai choisi (j'ai oublié le nom désolé) j'ai en effet besoin de récupéré une cellule particulière. J'ai déjà fait le script en Python (c'était mon premier projet en programmation ;) ) donc j'ai fait toute la structure en Haskell POUR cet algorithme. J'essaie quand même de m'abstenir de penser en Python sans quoi je n'apprendrai jamais Haskell correctement. En quoi est-ce mal de ne pas vérifier les bornes des coordonnées ? Je ne connais pas encore la gestion des exceptions en Haskell, mais si je ne m'abuse dans ce cas si Nothing est retourné.

@tanguy: Là ça marche, mais le but d'accepter les tuples d'Int comme argument était de pouvoir raccourcir la syntaxe (à la place de position). Merci quand même :)

Pas faux, je n'ai pas encore tout à fait compris les types, pour instancier (au sens OO commun) il faut faire Position (x, y) c'est ça ? C'est déjà plus court que le bazar avec Position { ... }

Tu peux lire ça et plus particulièrement ça et ça.

En quoi est-ce mal de ne pas vérifier les bornes des coordonnées ? Je ne connais pas encore la gestion des exceptions en Haskell, mais si je ne m'abuse dans ce cas si Nothing est retourné.

Tu ne retournes pas Nothing justement (je t'invites à vérifier le type de (!!)). Donc si tu demandes à acceder à un élément hors de la liste, tu lèves une exception.

Salut,

Désolé si je reviens si tardivement sur ce sujet, je voudrais ouvrir un autre topic mais ne pas clôturer celui-ci me paraissait impoli. J'ai fait un tour sur #haskell sur freenode, ils m'ont tous dit qu'il fallait mieux n'accepter qu'un type (que ça soit (Int, Int) ou Position). La seule chose que m'apporte le type Position, c'est de pouvoir clarifier la syntaxe, en mettant x position et y position au lieu de fst position et snd position. J'ai commencé un autre projet depuis, mais je ne l'ai pas abandonné, mais je pense tout simplement définir mes accesseurs comme ça:

1
2
3
4
5
type Position = (Int, Int)  -- Limite on peut s'en passer, mais c'est plus clair

x, y :: Position -> Int
x = fst
y = snd

En tout cas merci d'avoir pris la peine de prendre de votre temps pour m'aider,

AZ.

Edit: En fait ma prochaine question est assez facile (bien que je n'y trouve pas de réponse sur le net), devrais-je quand même ouvrir un nouveau topic ?

Edit 2: Bon, j'ai trouvé ma solution mon problème (-.-')

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte