Surcharge d'opérateur sur un type de donnée user-defined

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

Salut,

Etant en apprentissage depuis deux jours de Haskell, je me raccroche à mes habitudes de programmation en d'autres langages, principalement Python. Je suis donc habitué aux langages orientés objet, donc signalez moi si vous voyez une erreur de conception ou une architecture/vocabulaire non adapté au paradigme fonctionnel. Voici mon problème: j'essaie de définir un type de donnée dont on pourrait ajouter les instances avec l'opérateur classique +. Plus précisément, j'ai un type de donnée Position et un autre Direction.

Ils sont définis comme suit:

1
2
3
4
5
6
7
data Position = Position {
    x :: Int,
    y :: Int
} deriving Show

-- North | South | East | West
data Direction = N | S | E | W deriving Show

Maintenant j'aimerais que si je tape Position { x = 5, y = 6 } + N, j'obtienne Position { x = 5, y = 5 }.

J'ai essayé tout simplement de définir l'opérateur `(+)' avec la signature (+) :: Position -> Direction -> Position:

1
2
3
4
5
position + direction = case direction of
                           N -> Position { x = x position,     y = y position - 1 }
                           S -> Position { x = x position,     y = y position + 1 }
                           E -> Position { x = x position + 1, y = y position     }
                           W -> Position { x = x position - 1, y = y position     }

Evidemment Haskell me dit que c'est ambigu entre Prelude.+ et Tool.+ (Tool est le nom du module dans lequel sont définis mes types de donnée).

Je cherche donc un moyen de pallier à cette ambiguïté,

Merci d'avance,

AZ.

PS: Quel outil ZdS utilise-t-il pour la coloration syntaxique ? La "palette" des langages m'a l'air très étendue, en tout cas plus que celle d'OpenClassrooms :°

+0 -0

Haskell n'autorise pas ce genre de pratique en général. Si tu veux définir une fonction, il faut explicitement masquer l'ancienne. Ici tu pourrais essayer de commencer par

1
2
3
4
5
import Prelude hiding ((+), (++))
import qualified Prelude

(++) :: Num a => a -> a -> a
(++) = (Prelude.+)

et utiliser ++ dans la suite pour additionner des entiers - mais honnêtement, ne fais pas ça.

La bonne façon de définir un (+) est de définir une instance de la classe Num. La raison pour ça, c'est que c'est plus simple pour tout le monde, y compris pour les développeurs : quand on voit un + dans du code, on s'attend à ce qu'il additionne deux choses comparables, et même qu'il respecte les propriétés habituelles que l'on imagine bien. Dans ton cas, c'est impossible bien sûr.

En dehors du système de typage Haskell que je ne connais pas plus que ça (bon un petit peu quand même), il me parait étrange que tu additionne des positions et des directions. C'est comme additionner des choux et des bananes.

Il serait à mon avis plus intéressant de définir tes directions de la manière suivante :

1
2
3
4
N = Position { x = 0, y = 1 }
S = Position { x = 0, y = -1 }
E = Position { x = 1, y = 0 }
W = Position { x = -1, y = 0 }

EDIT : en utilisant des minuscules par contre car Haskell réserve les trucs qui commencent par des majuscules à des types apparemment.

Ou alors d'utiliser une fonction de conversion entre les deux types :

1
2
toPos :: Direction -> Position
toPos = -- ...

Je précise bien que mon avis n'a rien à voir avec le langage mais avec comment tu penses tes types. Est-ce que additionner une position et une direction a vraiment du sens ? Au niveau des types pas vraiment mais tu as moyen de faire cohabiter les deux avec cette deuxième méthode ou de n'utiliser qu'un seul type (moins générique et moins expressif) en utilisant la première.

ZdS utilise Pygments sinon, que OC utilisait à un moment avant de passer à un autre truc bizarre.

+1 -0

Vous avez raison additionner deux types différents c'est pas très naturel. Mais un autre opérateur aurait plus de sens. Cependant j'ai une autre question fortement liée. Dans une autre partie de mon programme (un générateur de labyrinthe), j'ai un type Maze qui représente un labyrinthe comme ceci:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
data MazeCell = MazeCell {
    position :: Position,
    filled   :: Bool,
    intact   :: Bool
}

data Maze = Maze {
    width  :: Int,
    height :: Int,
    cells  :: [[MazeCell]]
}

J'avais voulu surcharger l'opérateur !! pour obtenir l'objet MazeCell à une position donnée. Et bim, même problème… Pourtant ici l'opérateur a du sens, du coup y aurait-il une instance de laquelle on peut faire dériver Maze ? (Je ne comprend pas encore très bien type, class et instance, donc peut-être que ce que je dis n'a pas de sens :D )

Merci beaucoup pour vos réponses :)

Edit: peut être que katana a donné la bonne solution, je n'ai pas encore eu le temps de tester, je vois ça demain

+0 -0

Non, moi je t'ai dit de ne pas faire ça (réutiliser des noms déjà définis ailleurs). La solution de Microjoe est acceptable pour +, c'est à voir avec la définition de Num.

Il n'y a pas de "surcharge d'opérateur" en Haskell. Déjà, les opérateurs sont des fonctions comme les autres. Ensuite, ce que tu veux faire (du polymorphisme ad-hoc) ne peut se faire qu'à travers les typeclasses, pour les raisons que je t'ai données plus haut.

Ton deuxième exemple d'opérateur a davantage de sens, et, c'est vrai, on est surpris de voir que (!!) n'existe que pour les listes. En fait, c'est une sorte de punition que les Haskelleux s'auto-infligent : il ne faut pas oublier que (!!) n'est pas la bonne façon d'utiliser des listes.

Il me semble que l'opérateur d'indexation générique que tu cherches est plutôt noté (!) et il est décrit dans le module Data.Array. Cette fois-ci, ça n'est plus une mais deux typeclasses que tu vas devoir regarder : Ix et IArray. Quelle chance !

En dehors du système de typage Haskell que je ne connais pas plus que ça (bon un petit peu quand même), il me parait étrange que tu additionne des positions et des directions. C'est comme additionner des choux et des bananes.

MicroJoe

Juste pour dire qu'en mathématiques, il est courant de définir l'opération $A + \vec{u}$$A$ est un point et $\vec{u}$ un vecteur, contrairement à $A + B$ qui n'a pas de sens. Mais en effet, en Haskell mieux vaut utiliser un autre symbole que + pour implémenter une telle opération.

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