Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Defining instance with a type synonym

Apologies if this has been asked/answered many times over already -- I am having a hard time formulating what the problem actually is and thus didn't really know what to search for.


Essentially, I have a class I have defined such that :

class (MonadIO m) => Logger m where ...

And then I have a type (I want to say type synonym but I am not sure if this is the right 'term' ) :

type ResourceOpT r m a = StateT (ResourceCache r) m a

Why is it that this instance is perfectly valid :

instance (MonadIO m) => Logger ( StateT s m )

But not this one (I guess the first one is more abstract/preferrable but I'm trying to understand why) :

instance (MonadIO m) => Logger ( ResourceOpT r m )

Shouldn't both be equivalent by virtue of how I have defined ResourceOpT? Specifically, the error I am getting is :

  The type synonym 'ResourceOpT' should have 3 arguments, but has been given 2
  In the instance declaration for 'Logger (ResourceOpT r m)'

I have a feeling what I am doing 'should' conceptually work but either my syntax is wrong or there is something (maybe a language extension) that I am missing or should be enabling for this to work.

Regardless, I would be interested to get your input and learn why this is wrong and also why I should/should not be doing that.

Thanks in advance.

like image 703
Althar93 Avatar asked Apr 07 '21 22:04

Althar93


People also ask

What is the synonym for type?

Some common synonyms of type are character, description, kind, nature, and sort. While all these words mean "a number of individuals thought of as a group because of a common quality or qualities," type may suggest strong and clearly marked similarity throughout the items included so that each is typical of the group.

What do you mean by instance?

1 : a particular occurrence of something : example an instance of true bravery. 2 : a certain point or situation in a process or series of events In most instances, the medicine helps. instance. noun. in·​stance | \ ˈin-stəns \

What are antonyms for instance?

Antonyms. lengthen detach unclip failure ending appearance beginning.


1 Answers

The error reads:

The type synonym ResourceOpT should have 3 arguments

A type synonym (defined with type; you have the right term!) must be applied to the same number of type arguments as the number of parameters in its definition. That is, it’s sort of a “macro” for types, which is just substituted with its definition; it can’t be partially applied like a function can. In your case, ResourceOpT demands three arguments:

type ResourceOpT r m a = StateT (ResourceCache r) m a
              -- ^ ^ ^

This restriction makes it possible to do type inference with higher-kinded types, that is, things that abstract over type constructors like Monad and Foldable. Allowing type synonyms to be partially applied would mean that the compiler couldn’t deduce things like (m Int = Either String a) ⇒ (m = Either String, a = Int).

There are a few solutions. One is to start by directly addressing what the compiler is talking about, and change the number of parameters in the definition of ResourceOpT:

type ResourceOpT r m = StateT (ResourceCache r) m
              --    ^ ---------- no ‘a’ ----------^

Then, entering this code:

instance (MonadIO m) => Logger ( ResourceOpT s m )

Produces this different message:

Illegal instance declaration for Logger (ResourceOpT s m)

(All instance types must be of the form (T t1 ... tn) where T is not a synonym. Use TypeSynonymInstances if you want to disable this.)

If you use the -XTypeSynonymInstances compiler flag or {-# LANGUAGE TypeSynonymInstances #-} pragma in a source file, it allows making an instance for the type that a synonym expands to. This produces yet another message:

Illegal instance declaration for Logger (ResourceOpT s m) (All instance types must be of the form (T a1 ... an) where a1 ... an are distinct type variables, and each type variable appears at most once in the instance head. Use FlexibleInstances if you want to disable this.)

FlexibleInstances relaxes some restrictions on instances you can make. It shows up pretty often when writing certain kinds of code with monad transformers. Adding it, this code is accepted. What you’ve done here is make an instance of your Logger class for StateT s m for all s and m, provided that m is in MonadIO. If anyone wants to make a Logger instance for a different specialisation of StateT to something other than ResourceCache, then it will be rejected, or they’ll have to jump through some dubious hoops with overlapping instances.

One alternative that doesn’t require these extensions is to make a newtype instead of a type synonym:

newtype ResourceOpT r m a = ResourceOpT
  { getResourceOpT :: StateT (ResourceCache r) m a }

A newtype is, well, a new type, not a synonym. In particular, it’s a zero-cost wrapper for another type: same representation but different typeclass instances.

Doing this, you can write or derive instances of Applicative, Functor, Monad, MonadIO, MonadState (ResourceCache r), and so on, for the concrete type constructor ResourceOpT, just like all the other transformers in transformers like StateT, ReaderT, and so on. You can also partially apply the ResourceOpT constructor, because it’s not a type synonym.

And in general, the reason to have a Logger class is that you want to write code generic in the type of logger, because you have multiple different types that could be instances. But especially if ResourceOpT is the only one, then you can also do away with the class and write code in the concrete ResourceOpT, or a polymorphic m with a constraint such as MonadState (ResourceCache r) m. In general, a function parameter or polymorphic function is preferable to adding a new typeclass; however, without the details of the class definition and use case, it’s hard to say whether & how yours ought to be refactored.

like image 58
Jon Purdy Avatar answered Sep 30 '22 15:09

Jon Purdy