1

This is a follow-up to my previous quesion. I am reading this post again to understand the design described there.

They introduce a Job monad similar to Haxl. Job[T] is a data fetch operation that fetches data of type T (and may consist of other operations, i.e. it is a data fetches sequence).

sealed trait Job[+T]
case class PureJob[T](value: T) extends Job[T] 
case class BlockedJob[S,T](f: S => Job[T], job: Job[S]) extends Job[T]
case class FetchJob[T](url: Url) extends Job[T]

def pure[T](value: T): Job[T] = PureJob(value)

def flatMap[S,T](f: S => Job[T], job: Job[S]): Job[T] = 
  job match {
    case PureJob(value) => f(value)
    case _ => BlockedJob(f, job)
  }

They introduce also a function execute to actually execute a Job[T] operation and return a future.

def execute[T](job: Job[T])(implicit ec: ExecutionContext): Future[T] = { ... }

For concurrent data fetching they add new PairJob, and MapJob:

case class MapJob[S, T](f: S => T, job: Job[S]) extends Job[T]
case class PairJob[S, T](jobS: Job[S], jobT: Job[T]) extends Job[(S, T)]

Now they can write:

val jobA: FetchJob[A] = ...
val jobB: FetchJob[B] = ...
val f: A => B = ...

// jobAB is a MapJob of "f" and PairJob of jobA and jobB
val jobAB = (jobA |@| jobB) {(a, b) => f(a, b)}

My question is how to define Job[T] as Applicative to write code as in the example above.

2 Answers 2

2

Well has soon has you have PairJob, you have what you need for applicative.

With any generic type G, (here, that would be Job)

if you have pairing:

def pair[A,B](ga: G[A], gb: G[B]): G[(A,B)]

(which for Job, is just PairJob(ga, gb))

and also map, then you can easily implement apply

def ap[A, B](ga: ⇒ G[A])(f: ⇒ G[A ⇒ B]): G[B] = {
   val argAndFunc : G[(A, A => B)] = pair(ga, f)
   argAndFunc map {case (arg, func) => func(arg)}
}

The reverse is true, if you have ap and map, you easily get pair

def pair[A,B](ga: G[A], gb: G[B]): G[(A,B)] = {
  val pairWithB : G[B => (A,B]) = ga map {a => b: B => (a,b)}
  ap(gb)(pairWithB)
}
1

you can always define map in terms of flatMap and pure:

def map[A,B](fa: Job[A])(f: A=>B): Job[B] = fa flatMap (f andThen pure)

and then you can define ap in terms of map and flatMap:

def ap[A,B](fa: => Job[A])(f: => Job[A => B]): Job[B] = 
  fa flatMap { a =>
    f map (_(a))
  }

or with for comprehension sugar:

def ap[A,B](fa: => Job[A])(f: => Job[A => B]): Job[B] = 
  for {
    a <- fa
    ff <- f
  } yield ff(a)

or you can skip map:

def ap[A,B](fa: => Job[A])(f: => Job[A => B]): Job[B] =
  fa flatMap { a =>
    f flatMap { ff => pure(ff(a)) }
  }

Not the answer you're looking for? Browse other questions tagged or ask your own question.