Reader monad in Scala: return, local, and sequence

2019-06-26 01:11发布

问题:

I'm using the Reader monad in Scala as provided by the scalaz library. I'm familiar with this monad as defined in Haskell. The problem is that I cannot find the functions equivalent to return, local, and sequence (among others).

Currently I use constructs that I do not like since I'm repeating myself or making my code a bit obscure.

Regarding return, I'm currently using:

Reader{_ => someValue}

I'd rather just use a construct like unit(someValue), but I could not find anything on the internet. There are tutorials like this one that use the approach above, and which I consider not optimal.

Regarding local I also have to do something similar: instead of typing something like: local f myReader I have to unfold its definition:

Reader{env => myReader.run(f(env))

Finally, sequence is a bit closer to what I would expect (being a Haskell refugee doing Scala):

readers: List[Reader[Env, T]]
readerTs: Reader[Env, List[T]] = readers.sequenceU

My problem with this implementation is that, being relatively new to Scala, the type of sequenceU

final class TraverseOps[F[_],A] private[syntax](val self: F[A])(implicit val F: Traverse[F]) extends Ops[F[A]] {
    //...
    def sequenceU(implicit G: Unapply[Applicative, A]): G.M[F[G.A]]

appears like rather obscure, and seems like black magic. Ideally I would like to use a sequence operations on Monads.

Is there a better translation of these constructs to Scala available on scalaz or similar library? I'm not married to any Functional library for Scala, so any solution using other libraries will do, although I'd rather have an answer using scalaz, since I already implemented my code using it.

回答1:

To make the things simpler, I fill in some types. Changing them to defs with generic types should still work. Also I extracted the ReaderInt type, to avoid confusion with type lambdas.

return / pure / point

Scala does not have automatic typeclass resolution, so you need to provide them implicitly. For Kleisli (being a monad transformer for reader), Kleisli[Id, ?, ?] is enough

 implicit val KA = scalaz.Kleisli.kleisliIdApplicative[Int]
 type ReaderInt[A] = Kleisli[Id.Id, Int, A]

 val alwaysHello = KA.point("hello")  

or with imported syntax:

  import scalaz.syntax.applicative._  
  val alwaysHello = "hello".point[ReaderInt]

So as a general rule, you

1) import the applicative intance, which usually located in scalaz.std.something.somethingInstance

2) import scalaz.syntax.something._

3) then you can write x.point[F], where F is your applicative.

local

Not sure, that it answers your question, but Kleisli has a local method.

val f: String ⇒ Int = _.length
val alwaysEleven = alwaysHello local f

sequencing

The same way, you are free to choose to use syntax for or to specify type classes explicitly.

  import scalaz.std.list.listInstance
  val initial: List[ReaderInt[String]] = ???  
  val sequenced: ReaderInt[List[String]] = Traverse[List].sequence[ReaderInt, String](initial) 

  import scalaz.syntax.traverse._
  val z = x.sequence[ReaderInt, String]    

I prefer not to use sequenceU, which uses Unapply typelcass to infer the G type, because sometimes scala has troubles of figuring out the right one. And I personally do not find it messy to put in some types myself.

It may worth to look into cats, though it does not have much yet.