Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parallelism not working as expected #196

Closed
abdheshkumar opened this issue Apr 13, 2017 · 5 comments
Closed

Parallelism not working as expected #196

abdheshkumar opened this issue Apr 13, 2017 · 5 comments

Comments

@abdheshkumar
Copy link

abdheshkumar commented Apr 13, 2017

We recently found the weird behavior of parallelism with freestyle. Details below:

Algebra:

@free
  trait Validation[F[_]] {
    def minSize(n: Int): FreeS.Par[F, Boolean]
    def hasNumber: FreeS.Par[F, Boolean]
  }

Interpreter:

import cats.data.Kleisli

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

object Interpreters {

  import Algebra._

  type ParValidator[A] = Kleisli[Future, String, A]


  implicit val interpreter = new Validation.Handler[ParValidator] {
    def minSize(n: Int): ParValidator[Boolean] =
      Kleisli { s =>
        Future {
          println("x before")
          Thread.sleep(4000)
          println("x after")
          s.size >= n
        }
      }

    def hasNumber: ParValidator[Boolean] =
      Kleisli { s =>
        Future {
          println("y before")
          Thread.sleep(100)
          println("y after")
          s.exists(c => "0123456789".contains(c))
        }
      }
  }
}

Test1:

"program with for-expression running in nondeterminism style" in {
    import cats.implicits._
    import scala.concurrent.ExecutionContext.Implicits.global
    import Algebra._
    import Interpreters._
    import freestyle.nondeterminism._
    import freestyle.implicits._

    val validation = Validation[Validation.Op]
    import validation._
    val k = for {
      one <- (minSize(3) |@| hasNumber).map(_ :: _ :: Nil)
    }yield (one)
    val validator     = k.exec[ParValidator]
    Await.result(validator.run("a"), Duration.Inf) shouldBe List(false, false)
  }
Output:(nondeterminism)
x before
y before
y after
x after

Tes2:

"program with for-expression running in determinism style because of `tupled.freeS`" in {
    import cats.implicits._
    import scala.concurrent.ExecutionContext.Implicits.global
    import Algebra._
    import Interpreters._
    import freestyle.nondeterminism._
    import freestyle.implicits._

    val validation = Validation[Validation.Op]
    import validation._
    val k = for {
      one <- (minSize(3) |@| hasNumber).tupled.freeS
    }yield (one)
    val validator     = k.exec[ParValidator]
    Await.result(validator.run("a"), Duration.Inf) shouldBe (false, false)
  }

Output:(determinism)

x before
x after
y before
y after

We found that hasNumber is blocked till minSize is completed. So I think the issue is between map and tupled.freeS.
If you want to run a complete program, you can clone it from here https://github.com/abdheshkumar/freestyle-example/blob/master/src/test/scala/FreeFutureTest.scala

@peterneyens
Copy link
Member

This actually has nothing to do with map versus tupled, but with using freeS or not using .freeS.

Calling freeS on (minSize(3) |@| hasNumber).tupled lifts the FreeS.Par[Validation.Op, (Boolean, Boolean)]
into a FreeS[Validation.Op, (Boolean, Boolean)].

The difference between calling exec[ParValidator] on FreeS.Par or FreeS is that the first one uses the Applicative[ParValidator] instance where the second one uses the Monad[ParValidator] instance.

The problem is that the Monad instance of Kleisli doesn't override the product method, which is used by map and tupled of the cartesian builder. This means that the two Future operations are executed sequentially.

PR typelevel/cats#1618 should solve this in the next Cats version.

@peterneyens
Copy link
Member

Now that the Cats PR is merged, I am going ahead and close this issue.


If you want to try this before the next Cats release, you can provide your own Monad instance for Kleisli and use foldMap with a bit more work than exec.

val foo: FreeS[Validation.Op, (Boolean, Boolean)] =
  (minSize(3) |@| hasNumber).tupled.freeS

// a bit more work than  foo.exec[ParValidator] ...
val parValidatorMonad = kleisliMonad[Future, String]
val parInterpreter: ParInterpreter[Validation.Op, ParValidator] =
  interpretAp(parValidatorMonad, implicitly[Algebra.Validation.Handler[ParValidator]]))

val result: ParValidator[(Boolean, Boolean)] = foo.foldMap(parInterpreter)

def kleisliMonad[F[_], A](implicit F: Monad[F]) = new Monad[Kleisli[F, A, ?]] {
  def flatMap[B, C](fa: Kleisli[F, A, B])(f: B => Kleisli[F, A, C]): Kleisli[F, A, C] =
    fa.flatMap(f)

  def tailRecM[B, C](b: B)(f: B => Kleisli[F, A, Either[B, C]]): Kleisli[F, A, C] =
    Kleisli[F, A, C]({ a => F.tailRecM(b) { f(_).run(a) } })

  def pure[B](x: B): Kleisli[F, A, B] =
    Kleisli.pure[F, A, B](x)

  override def ap[B, C](f: Kleisli[F, A, B => C])(fa: Kleisli[F, A, B]): Kleisli[F, A, C] =
    fa.ap(f)

  override def product[B, C](fb: Kleisli[F, A, B], fc: Kleisli[F, A, C]): Kleisli[F, A, (B, C)] =
    Kleisli(a => F.product(fb.run(a), fc.run(a)))

  override def map[B, C](fa: Kleisli[F, A, B])(f: B => C): Kleisli[F, A, C] =
    fa.map(f)
}

@abdheshkumar
Copy link
Author

@peterneyens
I tried to run your posted code but it is giving me a compile errors. The complete code is here
https://github.com/abdheshkumar/freestyle-example/blob/master/src/test/scala/FreeFutureTest.scala#L89

Compilation Errors:

Error:(107, 7) no type parameters for method interpretAp: (implicit evidence$1: cats.Monad[M], implicit fInterpreter: cats.arrow.FunctionK[F,M])cats.arrow.FunctionK[[β$1$]cats.free.FreeApplicative[F,β$1$],M] exist so that it can be applied to arguments (cats.Monad[[γ$0$]cats.data.Kleisli[scala.concurrent.Future,String,γ$0$]], Algebra.Validation.Handler[Interpreters.ParValidator]) --- because --- argument expression's type is not compatible with formal parameter type; found : cats.Monad[[γ$0$]cats.data.Kleisli[scala.concurrent.Future,String,γ$0$]] required: cats.Monad[?M] interpretAp(parValidatorMonad, implicitly[Algebra.Validation.Handler[ParValidator]]) Error:(107, 19) type mismatch; found : cats.Monad[[γ$0$]cats.data.Kleisli[scala.concurrent.Future,String,γ$0$]] required: cats.Monad[M] interpretAp(parValidatorMonad, implicitly[Algebra.Validation.Handler[ParValidator]]) Error:(107, 48) type mismatch; found : Algebra.Validation.Handler[Interpreters.ParValidator] required: cats.arrow.FunctionK[F,M] interpretAp(parValidatorMonad, implicitly[Algebra.Validation.Handler[ParValidator]]) Error:(107, 18) type mismatch; found : cats.arrow.FunctionK[[β$1$]cats.free.FreeApplicative[F,β$1$],M] required: freestyle.ParInterpreter[Algebra.Validation.Op,Interpreters.ParValidator] (which expands to) cats.arrow.FunctionK[[β$1$]cats.free.FreeApplicative[Algebra.Validation.Op,β$1$],Interpreters.ParValidator] interpretAp(parValidatorMonad, implicitly[Algebra.Validation.Handler[ParValidator]])

@peterneyens
Copy link
Member

@abdheshkumar Specifying the type of the Monad instance seems to work:

val parValidatorMonad: Monad[ParValidator] = KleisliMonad.kleisliMonad[Future, String]

@abdheshkumar
Copy link
Author

abdheshkumar commented Apr 21, 2017

@peterneyens
Awesome! It works.
Thank you so much for help and support!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants