1

I know the title is a mouthful so let me explain.
I want to implement custom monad FileM by using the Cats Monad trait.
At the same time I want to define context bound for the type parameter so that FileM can only be used on types for which an instance of the Show typeclass exists.

I've tried the following:

case class FileM[T : Show] private (value: T)

object FileM {
  implicit val fileM: Monad[FileM] =
    new Monad[FileM] {

      override def pure[A](x: A): FileM[A] = {
        new FileM[A](x) // could not find implicit value for evidence parameter of type cats.Show[A]
      }

      override def flatMap[A, B](fa: FileM[A])(f: A => FileM[B]): FileM[B] = ???

      override def tailRecM[A, B](a: A)(f: A => FileM[Either[A, B]]): FileM[B] = ???
    }
}

I've tried adding the context bound to the pure method type parameter:

override def pure[A: Show](x: A): FileM[A] = {
  new FileM[A](x)
}

But this changes the signature so it no longer overrides the pure method from the Monad trait.

How can I add context bound to type A in this example?
I'm using scala 2.13.8 and cats-core:2.8.0

jcz
  • 903
  • 2
  • 9
  • 16
  • 2
    If you add a context bound to `FileM` then `FileM` is not a `Monad`. You violate the contract of type class `Monad`. – Dmytro Mitin Nov 02 '22 at 15:40
  • 1
    https://stackoverflow.com/questions/59113417/numeric-map-over-with-functor – Dmytro Mitin Nov 02 '22 at 15:42
  • https://hackage.haskell.org/package/constraint-classes-0.5.1/docs/Control-ConstraintClasses.html – Dmytro Mitin Nov 02 '22 at 15:56
  • 1
    You can ask for the evidence of show when generating the monad insatnce – Luis Miguel Mejía Suárez Nov 02 '22 at 16:16
  • @LuisMiguelMejíaSuárez Do you mean something like what cats does for Future’s ExecutionContext? If yes, I don’t think that would be possible here. – AminMal Nov 02 '22 at 16:49
  • @AminMal yeah that is precisely what I mean, it would not be possible with the code as it is right now, but maybe with some changes. Otherwise is not possible. – Luis Miguel Mejía Suárez Nov 02 '22 at 18:09
  • Can you also explain why you want all the instances to be showable? ( if you want) there might be a better way to do that. – AminMal Nov 02 '22 at 19:41
  • @AminMal, for educational purposes, I'm trying to define a monad `FileM` that persists the contained value to a file after each `map`. Just like IDE saves a file on every keystroke. I wanted to save the value as string, hence the requirement for `Show`. – jcz Nov 02 '22 at 19:55
  • https://ku-fpg.github.io/practice/constrainedTypeClassInstances/ – Dmytro Mitin Nov 02 '22 at 22:25
  • https://stackoverflow.com/questions/59192481/implementing-functor-map-for-class-tagged-arguments-only – Dmytro Mitin Nov 02 '22 at 23:21
  • 1
    https://stackoverflow.com/questions/48725356/how-to-implement-functordataset – Dmytro Mitin Nov 02 '22 at 23:23
  • http://blog.omega-prime.co.uk/2011/09/10/constraint-kinds-for-ghc/ https://stackoverflow.com/questions/68238411/functor-instance-for-gadts https://stackoverflow.com/questions/24000465/step-by-step-deep-explain-the-power-of-coyoneda-preferably-in-scala-throu https://medium.com/@olxc/yoneda-and-coyoneda-trick-f5a0321aeba4 – Dmytro Mitin Nov 03 '22 at 00:13
  • So generally there are two solutions: constrained/restricted/tagged type classes (like functor, monad) and Coyoneda (i.e. free functors)/free monads. – Dmytro Mitin Nov 03 '22 at 00:24
  • 1
    @DmytroMitin I also immediately thought about [that particular coyoneda trick](https://stackoverflow.com/questions/48725356/how-to-implement-functordataset), but genuinely wondering how you found it ;;; @jcz Re _"I'm trying to define a monad FileM that persists the contained value to a file after each map"_ - Can it then satisfy that `map(f).map(g)` = `map(g.compose(f))`? If not, then it's not a functor, and consequentially not a monad. I think one could do something like this with Coyoneda, but I'm not sure what exactly you're trying to achieve here. – Andrey Tyukin Nov 03 '22 at 00:56
  • @AndreyTyukin Looking for one's answer that you remember can be tricky at SO :) Isn't Coyoneda not enough? OP wants a monad, not functor. So free monads? – Dmytro Mitin Nov 03 '22 at 01:01
  • 1
    @DmytroMitin Yes, indeed, Coyoneda seems to be "too flat" / lacking a way to accumulate `flatMap`s (i.e. one would just re-invent something like a free monad). But, again, it would be a somewhat "distorted" free monad, with strange constraints on the `map`, right? Alongside with the `FlatMap`s, it would also accumulate some kind of `SaveToFile[A : Show]`-gizmos along the way. This seems like too high of a price for a little bit of syntactic convenience of doing the `map`ping and saving at once. I somehow feel that it's better to just make the saving step explicit. – Andrey Tyukin Nov 03 '22 at 01:15
  • @AndreyTyukin *"But, again, it would be a somewhat "distorted" free monad, with strange constraints on the map, right?"* Not sure. Coyoneda makes any type constructor a functor, right? (And context bound doesn't forbid that). And free monad makes any functor a monad, right? (And we already have a functor). – Dmytro Mitin Nov 03 '22 at 01:26
  • 1
    @DmytroMitin I was referring to the requirement that the OP mentioned in the 8th comment above: _"persists the contained value to a file after each map"_ - for this (assuming that "persisting" means having a `Show` instance), each `map`-invocation would somehow have to accept some extra information (`Show`) every time, that would make it just incompatible with standard `Monad`-interface altogether, regardless of the number of wrappers. But then, again, _"after each map"_ is somehow ill-defined anyway, so I'm still not sure what the OP actually wants. – Andrey Tyukin Nov 03 '22 at 01:33
  • 1
    I'm currently wondering: wouldn't it be fun to define something more heterogeneous than a monad: some kind of `Map[F[_], A, B] { def map(a: F[A], f: A => B): F[B]`-typeclass which is _explicitly parameterized_ by `A` and `B` (instead of just by `F[_]`), and then have _two separate_ instances, one high-priority for the cases where there is a `Show[B]`, and one low-priority for the cases where there isn't. Not sure yet what I'm actually after here, will take a look tomorrow. – Andrey Tyukin Nov 03 '22 at 01:43

0 Answers0