48 votes

Occurrences ambiguës en Haskell : comment les éviter ?

Je fais ce qui suit dans GHCI :

:m + Data.Map
let map = fromList [(1, 2)]
lookup 1 map

GHCI sait que map est un (Map Integer Integer). Alors pourquoi réclame-t-il une ambiguïté entre Prelude.lookup et Data.Map.lookup alors que le type est clair et que je peux l'éviter ?

<interactive>:1:0:
    Ambiguous occurrence `lookup'
    It could refer to either `Prelude.lookup', imported from Prelude
                          or `Data.Map.lookup', imported from Data.Map

> :t map
map :: Map Integer Integer
> :t Prelude.lookup
Prelude.lookup :: (Eq a) => a -> [(a, b)] -> Maybe b
> :t Data.Map.lookup
Data.Map.lookup :: (Ord k) => k -> Map k a -> Maybe a

59voto

Nathan Sanders Points 10641

Les types sont clairement différents mais Haskell n'autorise pas la surcharge ad-hoc des noms, vous ne pouvez donc en choisir qu'un seul. lookup à utiliser sans préfixe.

La solution typique consiste à importer Data.Map qualifié :

> import qualified Data.Map as Map

Alors vous pouvez dire

> lookup 1 [(1,2), (3,4)]
Just 2
> Map.lookup 1 Map.empty
Nothing

En général, les bibliothèques Haskell évitent de réutiliser les noms du prélude, ou bien en réutilisent un grand nombre. Data.Map est l'un des seconds, et les auteurs s'attendent à ce que vous l'importiez qualifié.

[Modifier pour inclure le commentaire d'ephemient]

Si vous voulez utiliser Data.Map.lookup sans le préfixe, vous devez cacher Prelude.lookup puisqu'il est implicitement importé sinon :

import Prelude hiding (lookup) 
import Data.Map (lookup)

C'est un peu bizarre mais cela pourrait être utile si vous utilisez Data.Map.lookup un tas de choses et vos structures de données sont toutes des maps, jamais des alists.

25voto

C. A. McCann Points 56834

D'un point de vue un peu plus général, c'est quelque chose qui m'a dérouté au début - alors, laissez-moi réitérer et souligner quelque chose que Nathan Sanders a dit :

Haskell ne permet pas la surcharge ad-hoc des noms.

Ceci est vrai par défaut, mais semble étonnamment peu évident au premier abord. Haskell autorise deux styles de fonctions polymorphes :

  • Polymorphisme paramétrique qui permet à une fonction d'opérer sur des types arbitraires d'une manière structurellement identique et abstraite.
  • Polymorphisme ad-hoc qui permet à une fonction d'opérer sur un ensemble défini de types d'une manière structurellement distincte mais, espérons-le, sémantiquement identique.

Le polymorphisme paramétrique est l'approche standard (et préférée si l'on a le choix) en Haskell et dans les langages apparentés ; le polymorphisme ad hoc est la norme dans la plupart des autres langages, sous des noms tels que "surcharge de fonctions", et est souvent mis en œuvre en pratique en écrivant plusieurs fonctions avec le même nom.

Le polymorphisme ad-hoc est activé en Haskell par classes de type qui exigent que la classe soit définie avec toutes ses fonctions polymorphes ad hoc associées, et que les instances soient explicitement déclarées pour les types utilisés par la résolution des surcharges. Les fonctions définies en dehors d'une déclaration d'instance ne sont jamais polymorphes ad-hoc. même si leurs types sont suffisamment distincts pour qu'une référence soit sans ambiguïté.

Ainsi, lorsque plusieurs fonctions non typiques avec des noms identiques sont définies dans différents modules, l'importation des deux modules sans qualification entraînera des erreurs si vous essayez d'utiliser l'une ou l'autre des fonctions. Les combinaisons de Data.List , Data.Map y Data.Set sont particulièrement flagrantes à cet égard, et parce que des parties de Data.List sont exportés par le Prelude, la pratique standard est (comme le dit Nathan Sanders) de toujours importer les autres qualifiés.

0 votes

C'est le genre de réponse que je cherchais, +1. Mais il me reste une question. Pourquoi, alors, il n'y a pas de classe de type "conteneur" pour toutes ces Data.List , Data.Set etc. Ou s'il y en a (et si je comprends bien, c'est le Functor typeclass) -- alors, pourquoi la définition de ses instances pour les types conteneurs n'est pas omniprésente dans les bibliothèques ?

0 votes

@ulidtko : La réponse courte est "parce que c'est plus difficile qu'il n'y paraît", et la réponse longue ne tiendrait pas dans un commentaire. Il y a beaucoup de complications impliquées dans quels conteneurs supportent quelles opérations et quelles limites sur les types d'éléments, &c. Cherchez des informations sur le site TypeFamilies Les API de type extension-conteneur en sont un exemple motivant.

3 votes

@ulidtko Cela pourrait être intéressant pour vous : hackage.haskell.org/packages/archive/classy-prelude/0.4.1/doc/

0voto

js2010 Points 823

Cela arrive tout le temps en utilisant le code des livres d'instruction. Vous pouvez toujours leur donner un nom différent. Méfiez-vous également des espaces unicode intégrés dans les ebooks.

sum2 []= 0 
sum2 (n:ns) = n + sum2 ns

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X