Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell Ambiguous Occurrences -- how to avoid?

Tags:

haskell

map

I do the following in GHCI:

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

GHCI knows that map is a (Map Integer Integer). So why does it claim an ambiguity between Prelude.lookup and Data.Map.lookup when the type is clear and can I avoid?

<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 
like image 976
me2 Avatar asked Jan 31 '10 22:01

me2


2 Answers

The types are clearly different but Haskell doesn't allow ad-hoc overloading of names, so you can only choose one lookup to be used without a prefix.

The typical solution is to import Data.Map qualified:

> import qualified Data.Map as Map 

Then you can say

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

Usually, Haskell libraries either avoid re-using names from the Prelude, or else re-use a whole bunch of them. Data.Map is one of the second, and the authors expect you to import it qualified.

[Edit to include ephemient's comment]

If you want to use Data.Map.lookup without the prefix, you have to hide Prelude.lookup since it's implicitly imported otherwise:

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

This is a bit weird but might be useful if you use Data.Map.lookup a whole bunch and your data structures are all maps, never alists.

like image 93
Nathan Shively-Sanders Avatar answered Sep 19 '22 23:09

Nathan Shively-Sanders


On a slightly more general note, this is something that confused me at first--so, let me reiterate and emphasize something Nathan Sanders said:

Haskell doesn't allow ad-hoc overloading of names

This is true by default, but seems surprisingly non-obvious at first. Haskell allows two styles of polymorphic functions:

  • Parametric polymorphism, which allows a function to operate on arbitrary types in a structurally identical, abstract manner
  • Ad-hoc polymorphism, which allows a function to operate on any of a defined set of types in a structurally distinct but, hopefully, semantically identical manner

Parametric polymorphism is the standard (and preferred given a choice) approach in Haskell and related languages; ad-hoc polymorphism is the standard in most other languages, going by names such as "function overloading", and is often implemented in practice by writing multiple functions with the same name.

Ad-hoc polymorphism is enabled in Haskell by type classes, which require the class to be defined with all of its associated ad-hoc polymorphic functions, and instances to be explicitly declared for the types used by overload resolution. Functions defined outside of an instance declaration are never ad-hoc polymorphic, even if their types are sufficiently distinct that a reference would be unambiguous.

So, when multiple non-type-class functions with identical names are defined in different modules, importing both modules unqualified will result in errors if you try to use either function. Combinations of of Data.List, Data.Map, and Data.Set are particularly egregious in this regard, and because parts of Data.List are exported by the Prelude, the standard practice is (as Nathan Sanders says) to always import the others qualified.

like image 31
C. A. McCann Avatar answered Sep 16 '22 23:09

C. A. McCann