Yann Moisan
Yann Moisan

Reputation: 8281

Seq[T => Reader[E, State[A, Unit]]] into Reader[E, A]

I've asked some questions recently to find a smart and readable way to solve this more complex problem.

I need a super combinator with this signature :

def runStateCombinator[T, E, A](t : T, readerFactories: Seq[T => Reader[E, State[A, Unit]]], f : E => A) : Reader[E, A] = {

Don't worry, it's just a stateful computation (State) that needs some deps (Reader).

Here is my current code, any improvement are more than welcome (I'm a scalaz n00b).

  def runStateCombinator[T, E, A](t : T, readerFactories: Seq[T => Reader[E, State[A, Unit]]], f : E => A) : Reader[E, A] = {
    def transpose[E, A](readers : Seq[Reader[E, A]]) : Reader[E, Seq[A]] =
      Reader { e: E => readers.map { r => r(e) } }

    def map2[E, A, B](reader: Reader[E, A])(f: (E, A) => B) : Reader[E, B] =
      Reader { e => f(e, reader(e)) }

    val readers = readerFactories.map(_.apply(t))
    val stateReader = Readers.transpose(readers).map(_.sequenceS_[A, Unit])
    Readers.map2(stateReader)((ctx, state) => state.run(f(ctx))._1)
  }

Upvotes: 0

Views: 56

Answers (1)

Odomontois
Odomontois

Reputation: 16328

In general monad transformers could be a savior, but in such small piece they could only create lots of boilerplate, especially without kind-projector.

More specifically your transpose is just alias for sequence, which could need additional Unapply in case of Reader And your map2 could be refactored to simple applicative ap Note also, that state.run(..)._1 already have alias exec

And I've add additional .toList as Seq is not represented well in scalaz

Overall my attempt to simplify is

import scalaz._
import scalaz.syntax.traverse._
import scalaz.std.list._
import syntax.monad._
import Id.Id

type ReadState[E, A, F[_]] = Reader[E, F[State[A, Unit]]]

def runStateCombinator[T, E, A](t: T, readerFactories: Seq[T => ReadState[E, A, Id]], f: E => A): Reader[E, A] = {
  val readers: ReadState[E, A, List] = readerFactories.map(_ (t)).toList.sequenceU
  val stateReader = readers.map(_.sequenceS_[A, Unit])
  stateReader <*> Reader(ctx => _.exec(f(ctx)))
}

The monad trasformer, for example could make whole sequenceU.map(_.sequenceS_single operation without need of explicit result types.

Another option is to use monstrous ReaderWriterState with empty writes:

import scalaz._
import scalaz.syntax.traverse._
import scalaz.std.list._
import Id.Id
import ReaderWriterStateT._
import scalaz.std.anyVal._

type ReadState[E, A, X] = ReaderWriterState[E, Unit, A, X]

def runStateCombinator[T, E, A](t: T, readerFactories: Seq[T => ReadState[E, A, Unit]], f: E => A): Reader[E, A] = {
  val readers = readerFactories.map(_ (t)).toList
  val stateReader = readers.sequence_[ReadState[E, A, ?], Unit](implicitly, rwstMonad[Id, E, Unit, A])
  Reader(x => stateReader.exec(x, f(x))._2)
}

Upvotes: 1

Related Questions