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?

arrowd
  • 33,231
  • 8
  • 79
  • 110
Saurabh Nanda
  • 6,373
  • 5
  • 31
  • 60
  • You can use `DerivingVia`: `newtype AppM features a = AppM (Env -> IO a) deriving (Functor, Applicative, Monad, MonadReader Env, MonadIO) via ReaderT Env IO` – Iceland_jack May 20 '22 at 16:01

1 Answers1

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.

Li-yao Xia
  • 31,896
  • 2
  • 33
  • 56
  • Thanks! I think the relevant section for me was https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#extension-GeneralizedNewtypeDeriving 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`? – Saurabh Nanda Jul 03 '19 at 11:18
  • 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. – Li-yao Xia Jul 03 '19 at 15:18
  • `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. – Li-yao Xia Jul 03 '19 at 15:24
  • 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. – Li-yao Xia Jul 03 '19 at 15:25
  • `-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. – dfeuer Jul 04 '19 at 17:41
  • 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 https://stackoverflow.com/questions/57198777/how-to-define-monadunliftio-instance-for-a-newtype-with-a-phantom-type-variable – Saurabh Nanda Jul 25 '19 at 09:38