15
votes

I'm trying to define a Foldable instance in Haskell and I have some problem with import.

So first try : module MyList where

import Data.Foldable

data MyList a = MyList [a]

instance Foldable (MyList) where
  foldr f b (MyList as) = foldr f b as

Result (normal but annoying)

Ambiguous occurrence `foldr'

So, I guess I have to hide it from the Prelude : module MyList where

import Prelude hiding (foldr)
import Data.Foldable

data MyList a = MyList [a]

instance Foldable (MyList) where
  foldr f b (MyList as) = foldr f b as

This compile, I load into ghci and try some basic stuff :

*MyList> foldr (:) "" (MyList "hello")
"hello"
*MyList> foldl (flip (:)) "" (MyList "hello")

<interactive>:1:0:
    Ambiguous occurrence `foldl'
    It could refer to either `Prelude.foldl', imported from Prelude at MyList.hs:4:0-28
                          or `Data.Foldable.foldl', imported from Data.Foldable at MyList.hs:5:0-19
*MyList>

So foldr works, but foldl doesn't. My first question is

Do I have to hide manually every single method defined in Data.Foldable from the Prelude is their a nice way to do it ?

.

To avoid this problem , I tried to do an qualified import : module MyList where

import qualified  Data.Foldable as F

data MyList a = MyList [a]

instance F.Foldable (MyList) where
  foldr f b (MyList as) = foldr f b as

Seems to compile in ghc but

*MyList> foldr (:) "" (MyList "hello")

<interactive>:1:14:
    Couldn't match expected type `[Char]'
           against inferred type `MyList Char'
    In the third argument of `foldr', namely `(MyList "hello")'
    In the expression: foldr (:) "" (MyList "hello")
    In the definition of `it': it = foldr (:) "" (MyList "hello")

foldr is not found but suprisingly F.foldr works in ghci.

*MyList> F.foldr (:) "" (MyList "hello")
"hello"

But in ghci only, if I'm trying to import MyList in file, foldr, F.foldr, MyList.F.foldr and MyList.foldr doesn't work.

Why does it work in ghci but not in real ?

I guess I have to import Data.Foldable again (and again in every files using MyList)

Is there a better way to do it (like exporting Data.Foldable in MyList) ?

(I'm a newbie in Haskell and especially with modules)


After having a couple of responses, it seems there is no clean solution to this problem. However, I'm pretty sure I'm not the first doing that, so

What is the common practice to deal with that kind of problem?

Thanks for you help.

5

5 Answers

2
votes

Here's a variant of Thomas Eding solution, with less typing. Basically, you can import (in Prelude') Prelude hiding some functions, and then reexport Prelude; this will export Prelude minus those functions. This technique is generally useful to write a frontend module re-exporting only some functions in a library.

You can then reexport also Data.Foldable.

Where I replaced some Prelude functions with equivalents (examples for Foldable and Category).

module Prelude2 (module Prelude, module Data.Foldable, module Control.Category) where

import Prelude hiding (id, (.), foldr)
import Data.Foldable (Foldable, foldr) -- You might want to import everything.
import Control.Category (Category((.), id))
-- Try out replacing with:
-- import Control.Category ()
-- to see that no (.) is available.

Usage:

module Foo where
import Prelude()
import Prelude2

To wit, notice the types of foldr and (.) in Foo, as shown by GHCi:

$ ghci Foo.hs
GHCi, version 7.6.3: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
[1 of 2] Compiling Prelude2         ( src-lib/Prelude2.hs, interpreted )
[2 of 2] Compiling Foo              ( /Users/pgiarrusso/AeroFS/Repos/pts-bluevelvet/src-lib/Foo.hs, interpreted )
Ok, modules loaded: Foo, Prelude2.
*Foo> :t foldr
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
*Foo> :t (.)
(.) :: Category cat => cat b c -> cat a b -> cat a c
*Foo>

If you try the suggestion of replacing

 import Control.Category ()

GHCi will show you that (.) is not defined at all in Foo:

*Foo> :r
[1 of 2] Compiling Prelude2         ( src-lib/Prelude2.hs, interpreted )
[2 of 2] Compiling Foo              ( /Users/pgiarrusso/AeroFS/Repos/pts-bluevelvet/src-lib/Foo.hs, interpreted ) [Prelude2 changed]
Ok, modules loaded: Foo, Prelude2.
*Foo> :t (.)

<interactive>:1:1: Not in scope: `.'
7
votes

Why does it work in ghci but not in real?

Because in your GHCi session you were typing expressions in the context of the MyList module, so F.foldr was in scope, but if you import MyList into another module then only the names exported by MyList, and the other modules you imported, are in scope.

Your guess is correct - in every module using Data.Foldable.foldr, you have to

import qualified Data.Foldable as F

The names exported by a module are unqualified; the qualification or not of those names is decided when the module is imported.

There have been proposals over the years to allow exporting qualified names, but to date nothing has been implemented.

6
votes

Regarding the implicit Prelude import, you could add the following language pragma, and then explicitly import things from Prelude, but it might get uglier than simply hiding things from the Prelude or using an qualified import of Data.Foldable.

{-# LANGUAGE NoImplicitPrelude #-}

import Prelude (someFunction)

Why it might get uglier? Because you might have to import data types and functions that are taken for granted, or even functions that aren't explicitly used in the code:

{-# LANGUAGE NoImplicitPrelude #-}
module Main (main) where

-- we import fromInteger although it's not explicitly used
import Prelude (Int, foldl, fromInteger, (+), putStrLn, (.), ($), IO, show)

sum :: [Int] -> Int
sum = foldl (+) 0

main :: IO ()
main = putStrLn . show $ sum [1,2,3]

I told you about this not because it's a good solution, but just to know that there's such a thing.

2
votes

You could make a Prelude' module that exports only the stuff you want. Then you could begin your actual module as follows:

import Prelude ()
import Prelude'
import Data.Foldable

Granted you have to do the grunt work in Prelude', but at least it is reusable.

Clarification:

Prelude'.hs:

module Prelude' (
    id
  , Num (..)
  , everthing you want exported for your 'customized' prelude module goes in this list
  ) where
2
votes

I've re-read your question and some of your comments and I guess the closest you can get to what you want is something like this:

module MyList
  (
    module Data.Foldable,
    MyList(..)
  )
where


import Data.Foldable
import Prelude (($))


data MyList a = MyList [a]

instance Foldable (MyList) where
    foldr f b (MyList as) = foldr f b as

Basically, MyList re-exports the Data.Foldable module, so that someone using your module won't have to import Data.Foldable again, but... she will have to hide some functions from the Prelude.

module Main (main) where


import Prelude hiding (foldr)
import MyList


mySum :: MyList Int -> Int
mySum = foldr (+) 0

main :: IO ()
main = putStrLn . show . mySum $ MyList [1,2,3]

IMHO, this is a good thing. You should not decide how and what someone imports in his own modules.