Skip to content

Commit

Permalink
added an extract method on Member and the possibility to
Browse files Browse the repository at this point in the history
transform a MemberIn instance in another one provided bijective
natural transformations between 2 effects.
  • Loading branch information
etorreborre committed Sep 12, 2016
1 parent 16cc62c commit c806d04
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 17 deletions.
59 changes: 45 additions & 14 deletions jvm/src/test/scala/org/atnos/eff/MemberSpec.scala
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
package org.atnos.eff

import cats.Eval
import cats.{Eval, ~>}
import cats.data._
import cats.implicits._
import org.specs2.{ScalaCheck, Specification}
import org.atnos.eff.all._
import interpret._
import syntax.all._
import org.specs2._
import org.scalacheck._
import org.specs2.concurrent.ExecutionEnv

import scala.concurrent.Future
import option._
import reader._
import state._
import syntax.all._

class MemberSpec(implicit ee: ExecutionEnv) extends Specification with ScalaCheck { def is = s2"""
class MemberSpec extends Specification with ScalaCheck { def is = s2"""

inject / project must work at the value level
for reader $reader
for writer $writer
for eval $eval

project fold (accept, inject) === identity $law
extract . inject === Option.apply $lawMemberIn
project fold (accept, inject) === identity $lawMember

A MemberIn instance can be transformed with natural transformations $nat

"""

Expand All @@ -32,6 +33,40 @@ class MemberSpec(implicit ee: ExecutionEnv) extends Specification with ScalaChec
def eval =
evalMember.project(evalMember.inject(eval1)).toEither must beRight(eval1)

def lawMemberIn =
writerMember.extract(writerMember.inject(write1)) ==== Option(write1)

def lawMember = Prop.forAll(genUnion, genMember) { (union: Union[S, String], m: SMember) =>
m.member.project(union).fold(m.member.accept, m.member.inject) ==== union
}

def nat = {
type readStr[E] = Reader[String, ?] |= E
type stateStr[E] = State[String, ?] |= E

def methodWithReadEffect[E: readStr: _option]: Eff[E, Int] =
for {
s <- ask[E, String]
_ <- option.some(s)
} yield s.size

implicit def readerStateNat[S1] = new (Reader[S1, ?] ~> State[S1, ?]) {
def apply[X](r: Reader[S1, X]): State[S1, X] =
State((s: S1) => (s, r.run(s)))
}

implicit def stateReaderNat[S1] = new (State[S1, ?] ~> Reader[S1, ?]) {
def apply[X](state: State[S1, X]): Reader[S1, X] =
Reader((s: S1) => state.runA(s).value)
}

def methodWithStateEffect[E](implicit state: State[String, ?] |= E, option: Option |= E): Eff[E, Int] =
methodWithReadEffect[E](state.transform[Reader[String, ?]], option)

methodWithStateEffect[Fx2[State[String, ?], Option]].runOption.evalState("hello").run ==== Option(5)

}

/**
* HELPERS
*/
Expand Down Expand Up @@ -59,10 +94,6 @@ class MemberSpec(implicit ee: ExecutionEnv) extends Specification with ScalaChec

}

def law = Prop.forAll(genUnion, genMember) { (union: Union[S, String], m: SMember) =>
m.member.project(union).fold(m.member.accept, m.member.inject) ==== union
}

def genUnion: Gen[Union[S, String]] =
Gen.oneOf(
writerMember.inject(write1),
Expand Down
4 changes: 2 additions & 2 deletions shared/src/main/scala/org/atnos/eff/Interpret.scala
Original file line number Diff line number Diff line change
Expand Up @@ -308,9 +308,9 @@ trait Interpret {

/**
* transform an effect into another one
* using a natural transformation
* using a natural transformation, leaving the rest of the stack untouched
*/
def transform[SR, BR, U, TS[_], TB[_], A](r: Eff[SR, A], nat: ~>[TS, TB])
def transform[SR, BR, U, TS[_], TB[_], A](r: Eff[SR, A], nat: TS ~> TB)
(implicit sr: Member.Aux[TS, SR, U], br: Member.Aux[TB, BR, U]): Eff[BR, A] = {

def go(eff: Eff[SR, A]): Eff[BR, A] = {
Expand Down
77 changes: 76 additions & 1 deletion shared/src/main/scala/org/atnos/eff/Member.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
package org.atnos.eff

import cats.data.Xor
import cats.~>

import scala.annotation.implicitNotFound

@implicitNotFound("No instance found for MemberIn[${T}, ${R}].\nThe effect ${T} is not part of the stack ${R}")
trait MemberIn[T[_], R] {
trait MemberIn[T[_], R] { outer =>
def inject[V](tv: T[V]): Union[R, V]
def extract[V](union: Union[R, V]): Option[T[V]]

def transform[O[_]](implicit to: T ~> O, from: O ~> T): MemberIn[O, R] = new MemberIn[O, R] {
def inject[V](ov: O[V]): Union[R, V] =
outer.inject(from(ov))

def extract[V](union: Union[R, V]): Option[O[V]] =
outer.extract(union).map(to.apply)
}
}

object MemberIn extends MemberInLower1 {
Expand All @@ -18,59 +29,119 @@ trait MemberInLower1 extends MemberInLower2 {
implicit def MemberIn1[T[_]]: MemberIn[T, Fx1[T]] = new MemberIn[T, Fx1[T]] {
def inject[V](effect: T[V]): Union[Fx1[T], V] =
Union1(effect)

def extract[V](union: Union[Fx1[T], V]): Option[T[V]] =
union match {
case Union1(e) => Option(e)
}

}
}

trait MemberInLower2 extends MemberInLower3 {
implicit def MemberIn2L[L[_], R[_]]: MemberIn[L, Fx2[L, R]] = new MemberIn[L, Fx2[L, R]] {
def inject[V](effect: L[V]): Union[Fx2[L, R], V] =
Union2L(effect)

def extract[V](union: Union[Fx2[L, R], V]): Option[L[V]] =
union match {
case Union2L(e) => Option(e)
case _ => None
}
}

implicit def MemberIn3L[L[_], M[_], R[_]]: MemberIn[L, Fx3[L, M, R]] = new MemberIn[L, Fx3[L, M, R]] {
def inject[V](effect: L[V]): Union[Fx3[L, M, R], V] =
Union3L(effect)

def extract[V](union: Union[Fx3[L, M, R], V]): Option[L[V]] =
union match {
case Union3L(e) => Option(e)
case _ => None
}
}

implicit def MemberInAppendL[T[_], L, R](implicit append: MemberIn[T, L]): MemberIn[T, FxAppend[L, R]] = new MemberIn[T, FxAppend[L, R]] {
def inject[V](effect: T[V]): Union[FxAppend[L, R], V] =
UnionAppendL(append.inject(effect))

def extract[V](union: Union[FxAppend[L, R], V]): Option[T[V]] =
union match {
case UnionAppendL(u) => append.extract(u)
case _ => None
}
}
}

trait MemberInLower3 extends MemberInLower4 {
implicit def MemberIn2R[L[_], R[_]]: MemberIn[R, Fx2[L, R]] = new MemberIn[R, Fx2[L, R]] {
def inject[V](effect: R[V]): Union[Fx2[L, R], V] =
Union2R(effect)

def extract[V](union: Union[Fx2[L, R], V]): Option[R[V]] =
union match {
case Union2R(e) => Option(e)
case _ => None
}
}

implicit def MemberIn3M[L[_], M[_], R[_]]: MemberIn[M, Fx3[L, M, R]] = new MemberIn[M, Fx3[L, M, R]] {
def inject[V](effect: M[V]): Union[Fx3[L, M, R], V] =
Union3M(effect)

def extract[V](union: Union[Fx3[L, M, R], V]): Option[M[V]] =
union match {
case Union3M(e) => Option(e)
case _ => None
}
}

implicit def MemberInAppendR[T[_], L, R](implicit append: MemberIn[T, R]): MemberIn[T, FxAppend[L, R]] = new MemberIn[T, FxAppend[L, R]] {
def inject[V](effect: T[V]): Union[FxAppend[L, R], V] =
UnionAppendR(append.inject(effect))

def extract[V](union: Union[FxAppend[L, R], V]): Option[T[V]] =
union match {
case UnionAppendR(u) => append.extract(u)
case _ => None
}
}
}

trait MemberInLower4 extends MemberInLower5 {
implicit def MemberIn3R[L[_], M[_], R[_]]: MemberIn[R, Fx3[L, M, R]] = new MemberIn[R, Fx3[L, M, R]] {
def inject[V](effect: R[V]): Union[Fx3[L, M, R], V] =
Union3R(effect)

def extract[V](union: Union[Fx3[L, M, R], V]): Option[R[V]] =
union match {
case Union3R(e) => Option(e)
case _ => None
}
}
}

trait MemberInLower5 {
implicit def MemberInAppendAnyL[T[_], R]: MemberIn[T, FxAppend[Fx1[T], R]] = new MemberIn[T, FxAppend[Fx1[T], R]] {
def inject[V](effect: T[V]): Union[FxAppend[Fx1[T], R], V] =
UnionAppendL(Union1(effect))

def extract[V](union: Union[FxAppend[Fx1[T], R], V]): Option[T[V]] =
union match {
case UnionAppendL(Union1(e)) => Option(e)
case _ => None
}
}

implicit def MemberInAppendAnyR[T[_], L, R](implicit m: MemberIn[T, R]): MemberIn[T, FxAppend[L, R]] = new MemberIn[T, FxAppend[L, R]] {
def inject[V](effect: T[V]): Union[FxAppend[L, R], V] =
UnionAppendR(m.inject(effect))

def extract[V](union: Union[FxAppend[L, R], V]): Option[T[V]] =
union match {
case UnionAppendR(u) => m.extract(u)
case _ => None
}
}
}

Expand All @@ -84,6 +155,10 @@ trait Member[T[_], R] extends MemberIn[T, R] {

def aux: Member.Aux[T, R, Out] =
this

def extract[V](union: Union[R, V]): Option[T[V]] =
project(union).toOption

}

object Member extends MemberLower1 {
Expand Down

0 comments on commit c806d04

Please sign in to comment.