I'm kind of new on purescript and I was experimenting with effects and particular async effects.
One of the things I love the most about FP and strict compilers like the one purescript has is that it enforces you to handle all possible results, in particular when you define that something can fail. If you for example are using an Either, you need to tell the program what to do in case you have the Right
answer or an error.
When I first looked at effects I liked the concept of actions
and handlers
and that if a part of your code needs to throw an exception (I imagine this is the last resource you want to use) you need to declare it using something like
someAction :: forall eff. Eff (exception :: EXCEPTION | eff)
and that you can define a handler that removes that effect so you know that from that point onwards you don't have to care about exceptions.
But doing some basic tests with the Aff
monad and the purescript-node-fs-aff
library I got some unexpected results.
If I do something like this
main :: forall e. Eff (console :: CONSOLE, buffer :: BUFFER, fs :: FS | e) Unit
main = do
_ <- launchAff $ readAndLog "./non-existing-file"
pure unit
readAndLog :: forall eff. String -> Aff (fs :: FS, console :: CONSOLE | eff) Unit
readAndLog path = do
str <- readTextFile UTF8 path
log str
If the file doesn't exists the program will terminate throwing an exception and there is nothing telling me that this code can fail, and that I should try to protect my program agaisnt that failure.
I can in fact be a little more defensive and use a catchError
, but I was expecting that at least the compiler fail saying I wasn't taking exception as a possible side effect.
main :: forall e. Eff (console :: CONSOLE, buffer :: BUFFER, fs :: FS | e) Unit
main = do
_ <- launchAff $ readAndLog "./non-existing-file" `catchError` (\e -> log ("buu: " <> message e))
pure unit
readAndLog :: forall eff. String -> Aff (fs :: FS, console :: CONSOLE | eff) Unit
readAndLog path = do
str <- readTextFile UTF8 path
log str
Ideally I would like to do something like Either
and be responsible to handle the particular errors the operation may have. For example when I read a file I should expect to have an error like ENOENT
(file does not exist) or EACCES
(you don't have access), etc. If I want to ignore the particular reason and just log that it failed it's my choice but the type system should enforce me to handle it.
launchAff
swallows the errors. It should require the caller to provide instructions (i.e. callback) for what to do with errors, but instead it just ignores them. - Fyodor Soikin