2

I am using the operator <|> for:

import qualified Data.ByteString.Lazy as B
import Network.HTTP.Conduit (simpleHttp)
import Data.Aeson
import Data.Maybe

data FooBar = FooBar {
    name :: !Text,
    surname :: !Text
    } deriving (Show,Generic)

instance FromJSON FooBar
instance ToJSON FooBar

getFeed :: String -> String -> IO (FooBar)
getFeed foo bar =  decode <$> (B.readFile foo <|> simpleHttp bar)

But when I try to compile it I get:

No instance for (Alternative IO) arising from a use of ‘<|>’
    In the second argument of ‘(<$>)’, namely
      ‘(B.readFile foo <|> simpleHttp bar)’
    In the expression:
      decode <$> (B.readFile foo <|> simpleHttp bar)
    In an equation for ‘getFeed’:
        getFeed env id
          = decode <$> (B.readFile foo <|> simpleHttp bar)

The error is a bit obscure to me. Any idea how to fix that? (BTW some insight from this reply: Confused by the meaning of the 'Alternative' type class and its relationship to other type classes)

duplode
  • 33,731
  • 7
  • 79
  • 150
Randomize
  • 8,651
  • 18
  • 78
  • 133
  • 2
    What are you trying to achieve with that `<|>`? – sinelaw Jul 07 '15 at 07:51
  • If the first "readFile" fails for some reason (like file not found) move to the second one, basically a generic alternative to "if exists then...else...". Although they look having same output (Bytestring) I think they need to "inherit" Alternative => in some way too. – Randomize Jul 07 '15 at 07:56

3 Answers3

3

Besides your actual problem, here's why IO can't be an Alternative.

First, what would you do for empty? It will need to have type forall a. IO a - an IO action that returns any type of value!

Second, how do you define failure (used for <|>)? For types such as Maybe, empty is obvious (Nothing) and failure can be defined as Nothing as well. So if the first argument is Nothing, return the second. But what about IO? The (confused) notion of failure on monads is magical and not represented at the type level, in other words, there is no explicit failure value.

It could work if you wrapped all your IO actions in Maybe (or Either) so that for example, readFile' :: FilePath -> IO (Maybe String). Then by lifting <|> you could combine actions. You would have to catch failures (exceptions) and translate them to Nothing in your wrapper.

(For convenience one could make an instance of Alternative for all Alternative f, Monad m => m f but that requires type composition? I'm not sure)

sinelaw
  • 16,205
  • 3
  • 49
  • 80
  • Thank you. Trying to apply to a monad IO context doesn't sound good unless I execute first every single possible operation and combine the result after (weird way to manage indeterminism) – Randomize Jul 07 '15 at 08:52
  • Your questions don't necessarily sound unanswerable. `empty` can throw an exception, and failure can be defined as "throws an exception". Perhaps things are a bit complicated with the modern exception mechanism, but probably "throws one of these five types of exception" is a pretty good answer, too. I believe there is even a `MonadPlus` instance for `IO` -- which has practically the same interface as `Alternative`. – Daniel Wagner Jul 07 '15 at 17:53
  • (Quick update to keep myself honest: I looked in `base` all the way back to version `3`, and didn't find a `MonadPlus` instance for `IO`. So that part of my previous comment is wrong.) – Daniel Wagner Jul 07 '15 at 17:58
  • According to Hackage, there've been `instance Alternative IO` since `base` version 4.9.0.0. It seems `empty` just raises an error with message `"mzero"`. But what does `<|>` do? – Dannyu NDos Jan 09 '18 at 09:29
  • 1
    @DannyuNDos - according to the source, `a <|> b` will run `a` and if it throws an exception, swallows the exception and runs `b`: https://hackage.haskell.org/package/base-4.10.1.0/docs/src/GHC.IO.html#mplusIO – sinelaw Jan 28 '18 at 14:36
2

The error just says that IO is not an instance of Alternative and so <|> is not defined for it, as you can see from documentation. Is your intended meaning "try B.readFile foo and if it fails, use simpleHttp bar instead"? If so, you can do

catch (B.readFile foo) (\(_ :: SomeException) -> simpleHttp bar)

(catch is from Control.Exception, and you should use Data.ByteString.Strict instead of Lazy to make sure exception gets thrown inside the scope of catch; you'll also need ScopedTypeVariables extension to write the above instead of something like \e -> let _ = e :: SomeException in simpleHttp bar).

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • Thank for your hint. I am trying to avoid program flows by exception cos I would like to have a "chain of <|>" (or similar). Anyway as you stated it might be a problem with "types". – Randomize Jul 07 '15 at 08:10
2

Here is an example of what you can do based on the blog post Playing Catch: Handling IO Exceptions with ErrorT.

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module Lib where

import qualified Data.ByteString.Lazy as B
import Network.HTTP.Conduit (simpleHttp)
import Control.Monad.Base
import Control.Applicative
import Control.Monad.Error
import System.IO.Error

newtype MyApp a = MyApp {
  getApp :: ErrorT String IO a
  } deriving (Functor, Applicative, Alternative, Monad, MonadIO, MonadError String, MonadBase IO)

myReadFile path = do
  r <- liftIO $ tryIOError $ B.readFile path 
  case r of
    Left e  -> throwError (strMsg "readFile error")
    Right x -> return x

mySimpleHttp bar = do
  r <- liftIO $ tryIOError $ simpleHttp bar
  case r of
    Left e -> throwError (strMsg "simpleHttp error")
    Right x -> return x

getFeed foo bar =  myReadFile foo <|> mySimpleHttp bar

runApp = runErrorT . getApp

doit = do result <- runApp $ getFeed "some/file.txt" "http://example.com/"
          case result of
            Left e  -> putStrLn $ "error: " ++ e
            Right r -> do putStrLn $ "got a result"; print r

I've been very explicit in this example - the article mentions ways you can reduce the amount of boiler-plate code.

My cabal build-depends: setting:

build-depends: base >= 4.7 && < 5, bytestring, mtl, http-conduit, transformers-base
ErikR
  • 51,541
  • 9
  • 73
  • 124
  • Thanks! very interesting suggestion and I liked the linked article too. It makes easy "centralise" exceptions running IO on the bottom of the stack. – Randomize Jul 07 '15 at 09:17