Closed hasufell closed 2 years ago
For the first problem, the issue is that the Monad interface isn't expressive enough: currently (>>=) :: m a -> (a -> m b) -> mb
but we would like (>>=) :: E e1 a -> (a -> E e2 b) -> E (F e1 e2) b
. It should be possible to work something with rebindable syntax (and to wait for https://github.com/ghc-proposals/ghc-proposals/pull/216 to make it more practical to use)
For the second problem, you mean real exception like IOException
?
For the second problem, you mean real exception like IOException?
Yes. I can catch them all and turn them into an error type, but it's never visible in the types.
For (1), I hope to find some time to try with qualified-do now that 9.0 is out.
For (2), isn't it that your definition of DownloadFailed
in your example with the forall
is too permissive? If you know the errors that will be encapsulated by DownloadFailed
beforehand, you can use splitVariant
to encapsulate them into DownloadFailed
and rethrow only the unhandled ones.
For IOException
, I fear you will have to import GHC.IO.Exception
and somehow lift the IOErrorType
to the type-level to integrate it with Excepts
.
Another thing I have no solution to is how to deal with tuples, when e.g. using sequenceE in ghcup I have this peculiar situation to return a tuple, e.g.:
VLeft (V (FileAlreadyExistsError fp, ())) -> do
runLogger $ logWarn $
"File " <> T.pack fp <> " already exists. Use 'ghcup install cabal --isolate " <> T.pack fp <> " --force ..." <> "' if you want to overwrite."
pure $ ExitFailure 3
VLeft e -> do
runLogger $ do
logError $ T.pack $ prettyShow e
logError $ "Also check the logs in " <> T.pack logsDir
pure $ ExitFailure 4
Now I would like a case to match on any tuple, like VLeft (V (e, ())
... but I'm unable to make that work. It always requires either VLeft e
or specifying the values inside the tuple.
It's usually difficult to work with generic types into variants. I think you could work something with a type-class matching these tuples and reduceVariant
for example. But this solution seems too heavy for your use case.
I don't understand why you use sequenceE
and hence build a product in the first place. Is it only to run setCabal
even if installCabal
failed? If it's the case, it could be simpler to do this explicitly:
v_install <- runE (installCabal...)
v_set <- runE (setCabal ...)
-- bundle both exceptions into one?
case (v_install,v_set) of
(VLeft e1, VLeft e2) -> throwE (InstallSetError e1 e2)
(VLeft e , _ ) -> throwE e
(_ , VLeft e ) -> throwE e
(VRight a, VRight b) -> pure (a,b)
-- drop second exception?
case (v_install,v_set) of
(VLeft e , _ ) -> throwE e
(_ , VLeft e ) -> throwE e
(VRight a, VRight b) -> pure (a,b)
It's usually difficult to work with generic types into variants. I think you could work something with a type-class matching these tuples and
reduceVariant
for example. But this solution seems too heavy for your use case.I don't understand why you use
sequenceE
and hence build a product in the first place. Is it only to runsetCabal
even ifinstallCabal
failed? If it's the case, it could be simpler to do this explicitly:v_install <- runE (installCabal...) v_set <- runE (setCabal ...) -- bundle both exceptions into one? case (v_install,v_set) of (VLeft e1, VLeft e2) -> throwE (InstallSetError e1 e2) (VLeft e , _ ) -> throwE e (_ , VLeft e ) -> throwE e (VRight a, VRight b) -> pure (a,b) -- drop second exception? case (v_install,v_set) of (VLeft e , _ ) -> throwE e (_ , VLeft e ) -> throwE e (VRight a, VRight b) -> pure (a,b)
That doesn't seem to typecheck:
• V '[NotInstalled] not found in list:
'[AlreadyInstalled, ArchiveResult, BuildFailed, CopyError,
DigestError, DirNotEmpty, DownloadFailed, FileAlreadyExistsError,
FileDoesNotExistError, GPGError, MergeFileTreeError,
NextVerNotFound, NoDownload, NoToolVersionSet, NotInstalled,
ProcessError, TagNotFound, TarDirDoesNotExist,
UninstallFailed, UnknownArchive]
That doesn't seem to typecheck:
Ah yes, my bad. I guess we need something like the following to replace throwE
:
-- | Throw some exception
throwSomeE :: forall es' es a m. (Monad m, LiftVariant es' es) => V es' -> Excepts es m a
{-# INLINABLE throwSomeE #-}
throwSomeE = Excepts . pure . VLeft . liftVariant
Excellent. That seems to work. I'd say we should add throwSomeE
then.
I'm envisioning an exception system where I rethrow low-level exceptions as high-level exceptions, while still encapsulating the underlying error. An example:
There are currently two problems with this approach:
This makes it impossible to know which errors are "lies" after a refactor.
Any ideas?