Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I break up a Haskell statement when the type of a parameter is not static?

Tags:

haskell

This is a bit difficult to explain, but I've run into this situation a few times.

The code is as follows:

work :: String -> IO ()
work a = do
  input <- lines <$> getContents
  sortF <- let f = flip sortByM input
    in case a of
        "name" -> f (return . id         :: FilePath -> IO FilePath)
        "time" -> f (getModificationTime :: FilePath -> IO UTCTime)
        _ ->      f (getModificationTime :: FilePath -> IO UTCTime)
  print sortF

sortByM :: (Monad m, Ord a) => (b-> m a) -> [b] -> m [b]
sortByM f x = do
  x' <- mapM f x
  return $ fst <$> (sortBy (comparing snd) $ zip x x')

The above throws an error of:

• Couldn't match type ‘UTCTime’ with ‘[Char]’
  Expected type: String -> IO FilePath
    Actual type: FilePath -> IO UTCTime
• In the first argument of ‘f’, namely
    ‘(getModificationTime :: FilePath -> IO UTCTime)’
  In the expression:
    f (getModificationTime :: FilePath -> IO UTCTime)
  In a case alternative:
      "time" -> f (getModificationTime :: FilePath -> IO UTCTime)

Which makes sense, but is there a way to somehow achieve the above? Otherwise I have to do the below, which feels less maintainable:

work :: String -> IO ()
work a = do
  input <- lines <$> getContents
  sortF <- case a of
        "name" -> flip sortByM input (return . id         :: FilePath -> IO FilePath)
        "time" -> flip sortByM input (getModificationTime :: FilePath -> IO UTCTime)
        _ ->      flip sortByM input (getModificationTime :: FilePath -> IO UTCTime)
  print sortF

sortByM :: (Monad m, Ord a) => (b-> m a) -> [b] -> m [b]
sortByM f x = do
  x' <- mapM f x
  return $ fst <$> (sortBy (comparing snd) $ zip x x')
like image 241
Chris Stryczynski Avatar asked Mar 10 '23 01:03

Chris Stryczynski


2 Answers

You are running into the Dreaded Monomorphism Restriction. Due to issues with optimisation and code generation, when GHC sees a value that doesn't have explicit arguments it will infer a monomorphic type for it rather than the more general polymorphic type you would expect.

You can either disable the restriction using the NoMonomorphismRestriction pragma, or you can give f an explicit parameter:

sortF <- let f xs = sortByM xs input in ...
like image 51
Paul Johnson Avatar answered Apr 30 '23 11:04

Paul Johnson


Making some assumptions about what you want... there tends to be lots of options on code structure. You can have a sum type (not a great solution here):

  ...
  sortF <- let f = flip sortByM input
    in case a of
        "name" -> Left <$> f (return . id         :: FilePath -> IO FilePath)
        "time" -> Right <$> f (getModificationTime :: FilePath -> IO UTCTime)
        _ ->      Right <$> f (getModificationTime :: FilePath -> IO UTCTime)
  either print print sortF

Since the only thing you really know about a "multi-typed" sortF is that it is an instance of Show you can just call show and use String:

  ...
  sortF <- let f = flip sortByM input
    in show <$> case a of
        "name" -> f (return . id         :: FilePath -> IO FilePath)
        "time" -> f (getModificationTime :: FilePath -> IO UTCTime)
        _ ->      f (getModificationTime :: FilePath -> IO UTCTime)
  putStrLn sortF

And you can use a more function-oriented approach in combination with unifying the type to String:

  ...
  let op | a == "name" = return . show
         | otherwise   = fmap show . getModificationTime
      f x = sortByM x input
  putStrLn =<< f =<< op
like image 26
Thomas M. DuBuisson Avatar answered Apr 30 '23 10:04

Thomas M. DuBuisson