1
votes

This is a question about the use of type classes in Haskell.

I get an error (below) when trying to compile the code (below and at https://github.com/chrisdew/haskell-sandbox/blob/master/not_working_but_clean.hs ).

As someone just learning Haskell, I have tried following GHC's advice, but I think the cause is different.

I believe the problem is that either of the types 'IO String' or plain 'String' could be the type of 'lhello ->> lbracket', but GHC doesn't know which.

The problem is that it doesn't matter, either type would work fine.

I have posted a working version of the code at https://github.com/chrisdew/haskell-sandbox/blob/master/working_but_ugly.hs . This replaces one of the ->> operators with a new (non type class) operator '->>>' which forces 'lhello ->> lbracket' to be of type 'IO String'.

  • Is my analysis correct? Or is there something else going on here?

  • Is there any way of informing GHC what the type of 'lhello ->> lbracket' doen't matter and that is should just chose either of the two possibilities. Or perhaps theres a LANGUAGE option which will let me specify that 'lastest declared matching instance of the class wins' if anything is undecided.

Thanks,

Chris.

Error:

chris@chris-linux-desktop:~/nonworkspace/haskell-sandbox$ ghc
not_working_but_clean.hs

not_working_but_clean.hs:40:16:

   No instance for (Stream (IO String) (IO String) (IO String) d)
     arising from a use of '->>' at not_working_but_clean.hs:40:16-34
   Possible fix:
     add an instance declaration for
     (Stream (IO String) (IO String) (IO String) d)
   In the first argument of '(->>)', namely 'lhello ->> lbracket'
   In the second argument of '($)', namely
       'lhello ->> lbracket ->> putStrLn'
   In a stmt of a 'do' expression:
         forkIO $ lhello ->> lbracket ->> putStrLn

not_working_but_clean.hs:40:16:
   No instance for (Stream d String (IO ()) (IO ()))
     arising from a use of `->>' at not_working_but_clean.hs:40:16-47
   Possible fix:
     add an instance declaration for (Stream d String (IO ()) (IO ()))
   In the second argument of `($)', namely
       `lhello ->> lbracket ->> putStrLn'
   In a stmt of a 'do' expression:
         forkIO $ lhello ->> lbracket ->> putStrLn
   In the expression:
       do { forkIO $ (bracket $ hello) ->> putStrLn;
            forkIO $ lhello ->> lbracket ->> putStrLn;
        forkIO $ bracket hello ->> putStrLn;
            forkIO $ lbracket lhello ->> putStrLn;
          .... }

not_working_but_clean.hs:

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances,
TypeSynonymInstances, OverlappingInstances #-}
{-# OPTIONS_GHC #-}

module Main (
main
)
where

import Control.Concurrent (forkIO, MVar, newEmptyMVar, putMVar,
takeMVar, ThreadId, threadDelay)
import Control.Monad (forever, liftM)

class Stream a b c d where
   (->>) :: a -> (b -> c) -> d

instance Stream (IO d) d (IO c) (IO c) where
   f ->> g = f >>= g

instance Stream d d (IO c) (IO c) where
   f ->> g = g f

instance Stream d d c c where
   x ->> y = y $ x

-- This simply wraps a string in brackets.
bracket :: String -> String
bracket x = "(" ++ x ++ ")"

lbracket :: IO String -> IO String
lbracket x = liftM bracket x

hello :: String
hello = "Hello World!"

lhello :: IO String
lhello = do return hello

main :: IO ()
main = do
      forkIO $ (bracket $ hello) ->> putStrLn
      forkIO $ lhello ->> lbracket ->> putStrLn
      forkIO $ bracket hello ->> putStrLn
      forkIO $ lbracket lhello ->> putStrLn
      threadDelay 10000000 -- Sleep for at least 10 seconds before exiting.

working_but_ugly.hs:

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances,
TypeSynonymInstances, OverlappingInstances #-}
{-# OPTIONS_GHC #-}

module Main (
main
)
where

import Control.Concurrent (forkIO, MVar, newEmptyMVar, putMVar,
takeMVar, ThreadId, threadDelay)
import Control.Monad (forever, liftM)

class Stream a b c d where
   (->>) :: a -> (b -> c) -> d

instance Stream (IO d) d (IO c) (IO c) where
   f ->> g = f >>= g

instance Stream d d (IO c) (IO c) where
   f ->> g = g f

instance Stream d d c c where
   x ->> y = y $ x

x ->>> y = y $ x

-- This simply wraps a string in brackets.
bracket :: String -> String
bracket x = "(" ++ x ++ ")"

lbracket :: IO String -> IO String
lbracket x = liftM bracket x

hello :: String
hello = "Hello World!"

lhello :: IO String
lhello = do return hello

main :: IO ()
main = do
      forkIO $ (bracket $ hello) ->> putStrLn
      forkIO $ lhello ->>> lbracket ->> putStrLn
      forkIO $ bracket hello ->> putStrLn
      forkIO $ lbracket lhello ->> putStrLn
      threadDelay 10000000 -- Sleep for at least 10 seconds before exiting.
1
I feel frightened when I see type classes with multiple totally independent type arguments. From a brief overlook I'd say that c depends on b and d is always the same as c. Perhaps give it a try with appropriate functional dependencies and one type less, maybe things will get easier then. EDIT: also b seems to depend on aIngo
@lngo a and b have almost the same type, they are constrained to: a == b || IO a == b || a == IO b. c and d have the same constraint. I don't know how to form this constraint in Haskell.fadedbee
Multiparameter type classes without functional dependencies is just asking for trouble, as you noticed.augustss

1 Answers

4
votes

No, there is no way to make GHC flip a coin and pick one.

All of your instances have type 'c' the same as type 'd', so you could probably omit type 'd' and reuse type 'c' in defining Stream.

instance Stream d d (IO c) (IO c) where
   f ->> g = g f

instance Stream d d c c where
   x ->> y = y $ x

The above are the same. "g f" and "y $ x" are the same. So why two different instances??