Is it safe to derive MonadThrow, MonadCatch, MonadBaseControl, MonadUnliftIO, etc?
Asked Answered
C

1

1

I'm refactoring some old code, which is in a polymorphic, but type-class constrained, monad:

class ( MonadIO m
      , MonadLogger m
      , MonadLoggerIO m
      , MonadThrow m
      , MonadCatch m
      , MonadMask m
      , MonadBaseControl IO m
      , MonadUnliftIO) => HasLogging m where

In the older code the application's main monad was...

type AppM = ReaderT Env IO

...which will now change to...

newtype AppM (features :: [FeatureFlag]) a = AppM (ReaderT Env IO a)
  deriving (Functor, Applicative, Monad, MonadReader Env, MonadIO)

Given this context, is it safe to derive the following, automatically:

  • MonadThrow
  • MonadCatch
  • MonadMask
  • MonadBaseControl
  • MonadUliftIO

Without getting into GHC internals, what's the best way to develop intuition about what's actually happening when the compiler derives things automagically?

Coccus answered 3/7, 2019 at 5:59 Comment(1)
You can use DerivingVia: newtype AppM features a = AppM (Env -> IO a) deriving (Functor, Applicative, Monad, MonadReader Env, MonadIO) via ReaderT Env IOHumid
T
1

The user manual has documentation about every extension, and it keeps getting better; here's the section on deriving, that should be sufficient to know what's actually happening: https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#extensions-to-the-deriving-mechanism

In this case, all those classes are handled by GeneralizedNewtypeDeriving.

{-# LANGUAGE GeneralizedNewtypeDeriving, UndecidableInstances #-}

module M where

import Control.Monad.IO.Unlift
import Control.Monad.Catch
import Control.Monad.Trans.Control
import Control.Monad.Base
import Control.Monad.Reader

newtype Foo a = Foo (ReaderT () IO a)
  deriving (Functor, Applicative, Monad, MonadIO, MonadUnliftIO, MonadThrow, MonadCatch, MonadMask, MonadBase IO, MonadBaseControl IO)

In general, the three relevant extensions for user-defined classes are GeneralizedNewtypeDeriving, DerivingVia, and DeriveAnyType. And it's also worth enabling DerivingStrategies to make it explicit which is being used.

Tamatamable answered 3/7, 2019 at 10:59 Comment(6)
Thanks! I think the relevant section for me was downloads.haskell.org/~ghc/latest/docs/html/users_guide/… I now understand that instances can be derived pretty mechanically for simple newtypes. However, in my case the newtype also has a phantom type, which might complicate things. In fact, it refuses to derive anything for MonadBaseControl IO. Is it safe to assume that the compiler is doing the safe thing if it derives something automatically? Specifically for things like MonadCatch and MonadMask?Coccus
I'm not sure what you mean, phantom types shouldn't be a problem at all, unless you mean something different than I have in mind. Deriving is type-safe, it doesn't do anything you couldn't do manually without deriving. But type-safe doesn't mean correct, and correctness is up to you as the programmer to specify. What GeneralizedNewtypeDeriving and DerivingVia do is entirely straightforward, assuming you know how to wrap an interface in a newtype, which sounds like pretty basic knowledge, but tell me if I'm missing something.Tamatamable
DeriveAnyType uses the "default" implementation of a class, which is part of the definition of the class (and may not exist). So to know whether that is correct, you have to look at the default implementation (or trust what the doc says). The point is that there is nothing you need to assume as a programmer using deriving. You need to know how to write an instance, and you need to know what instance gets derived to know whether that's the instance that you wanted to write. That doesn't involve any knowledge of the internals of GHC.Tamatamable
If you are unsure (which is understandable at the start), you can also use the GHC option -ddump-deriv to check what instance is actually being derived.Tamatamable
-ddump-deriv results may well refer to functions with rather mysterious names that are defined/generated in other modules. It's useful for stock deriving, but I dunno how much you'll get out of it otherwise.Blasien
Now that my refactoring process is on the verge of finishing, my final code is emerging and I have run into a related, but possibly, different problem at #57199277Coccus

© 2022 - 2024 — McMap. All rights reserved.