I am not yet super strong with the concept of effects, so some of my assumptions might be completely wrong. Please correct me whenever you see such occurrences.
I am building an application (not from scratch, but rather developing the skeleton) with scala-cats and cats-effects. The main class extends IOApp and starts a web server:
object Main extends IOApp {
override def run(args: List[String]): IO[ExitCode] =
new Application[IO]
.stream
.compile
.drain
.as(ExitCode.Success)
}
class Application[F[_]: ConcurrentEffect: Timer] {
def stream: Stream[F, Unit] =
for {
// ...
} yield ()
}
This is the first encounter with the F[_] type. The : ConcurrentEffect: Timer context-bound says that there are two instances declared somewhere: ConcurrentEffect[F[_]] and Timer[F[_]] if I understand that correctly.
Skipping the HTTP layer of application, route handler uses the service I am trying to implement with two different variations - DummyService and LiveService - Dummy is supposed to always return the constant (dummy) data, whilst Live one sends a REST request and parses the JSON response to internal domain models:
trait CurrencyConverterAlgebra[F[_]] {
def get(currency: Currency): F[Error Either ExchangeRate]
}
class DummyCurrencyConverter[F[_]: Applicative] extends CurrencyConverterAlgebra[F] {
override def get(currency: Currency): F[Error Either ExchangeRate] =
ExchangeRate(BigDecimal(100)).asRight[Error].pure[F]
}
object DummyCurrencyConverter {
// factory method
def apply[F[_]: Applicative]: CurrencyConverterAlgebra[F] = new DummyCurrencyConverter[F]()
}
So far so good. The only bit of mystery to me is why we have to have that Applicative implicit.
But now I try to implement the Live service which will also make use of the Cache (to throttle the requests):
trait Cache[F[_], K, V] {
def get(key: K): F[Option[V]]
def put(key: K, value: V): F[Unit]
}
private class SelfRefreshingCache[F[_]: Monad, K, V]
(state: Ref[F, Map[K, V]], refresher: Map[K, V] => F[Map[K, V]], timeout: FiniteDuration) extends Cache[F, K, V] {
override def get(key: K): F[Option[V]] =
state.get.map(_.get(key))
override def put(key: K, value: V): F[Unit] =
state.update(_.updated(key, value))
}
object SelfRefreshingCache {
def create[F[_]: Monad: Sync, K, V]
(refresher: Map[K, V] => F[Map[K, V]], timeout: FiniteDuration)
(implicit timer: Timer[F]): F[Cache[F, K, V]] = {
def refreshRoutine(state: Ref[F, Map[K, V]]): F[Unit] = {
val process = state.get.flatMap(refresher).map(state.set)
timer.sleep(timeout) >> process >> refreshRoutine(state)
}
Ref.of[F, Map[K, V]](Map.empty)
.flatTap(refreshRoutine)
.map(ref => new SelfRefreshingCache[F, K, V](ref, refresher, timeout))
}
}
Here, SelfRefreshingCache requires Sync instance to be present - otherwise I am getting an error saying it is not defined when trying to construct a Ref instance. Also, in order to be able to use the state.get.map(_.get(key)) statement in the SelfRefreshingCache class, I have to use the Monad constraint, presumingly to tell Scala that my F[_] type inside Cache can be flatMap-ped.
In my Live service I am trying to use this service as follows:
class LiveCurrencyConverter[F[_]: Monad](cache: F[Cache[F, Currency, ExchangeRate]]) extends Algebra[F] {
override def get(currency: Currency): F[Error Either ExchangeRate] =
cache.flatMap(_.get(currency))
.map(_.toRight(CanNotRetrieveFromCache()))
}
object LiveCurrencyConverter {
def apply[F[_]: Timer: ConcurrentEffect]: Algebra[F] = {
val timeout = Duration(30, TimeUnit.MINUTES)
val cache = SelfRefreshingCache.create[F, Currency, ExchangeRate](refreshExchangeRatesCache, timeout)
// ---> could not find implicit value for evidence parameter of type cats.Monad[Nothing]
new LiveCurrencyConverter(cache)
}
private def refreshExchangeRatesCache[F[_]: Monad: ConcurrentEffect](existingRates: Map[Currency, ExchangeRate]): F[Map[Currency, ExchangeRate]] = ???
}
Currently, I am stuck at getting compilation error saying I don't have an instance of Monad[Nothing]. And this is where my whole story Main turns around: if I understand the whole concept behind type constraints (requiring implicits to be defined in the scope of a method call), then the F[_] type should be propagated from the very Main level down to my Live service and should be something like IO. And IO has both map and flatMap methods defined. On a Live service level, the refreshExchangeRatesCache makes a REST call (using the http4s, but that should not matter) and is supposed to run on something like IO as well.
First of all, are my assumptions about context boundaries and F[_] propagation from the Main class correct? Can I then hide the IO type on the Live service level? Or how do I provide the required Monad implicit instance?
This is the first encounter with the F[] type. The :
ConcurrentEffect: Timer context-bound says that there are two
instances declared somewhere: ConcurrentEffect[F[]] and Timer[F[_]]
if I understand that correctly.
To be specific, it has to be declared inside the implicit scope.
The only bit of mystery to me is why we have to have that Applicative
implicit.
You need evidence of an Applicative[F] because your method uses pure[F] to lift
ExchangeRate onto F, where pure is defined in the Applicative typeclass:
ExchangeRate(BigDecimal(100)).asRight[Error].pure[F]
Also, in order to be able to use the state.get.map(_.get(key))
statement in the SelfRefreshingCache class, I have to use the Monad
constraint
Since you're using .map and not .flatMap, it will suffice to require an instance of Functor and not Monad for the class definition of SelfRefreshingCache. For the companion object, you'll need a Monad in order to flatMap.
First of all, are my assumptions about context boundaries and F[_]
propagation from the Main class correct?
Yes, they are. When you build your entire program in Main, and "fill in" IO where F[_] is required, the compiler will search for the existence of all the implicit evidence required from IO in scope, given that you've captured the requirements from each method invocation using context bounds or plain implicit parameters.
Can I then hide the IO type on the Live service level?
The IO is hidden in your approach, as Live only knows the "shape" of the type, i.e. F[_]. Requiring the immediate solution to your problem, the previous answer has stated you need to add F to your method call in order for the compiler to infer which type you meant to fill in refreshExchangeRatesCache.
Add the type information, in this case F, to the line
val cache = SelfRefreshingCache.create[F, Currency, ExchangeRate](refreshExchangeRatesCache[F], timeout)
Related
I'm designing sort of Services and faced with a design issue. Here is what I currently have:
trait Service {
def isFailed(): Boolean
def start(): Unit
def stop(): Unit
}
And in order to group Services related to each other in a group (in order to restart/recover the group, not other services) I created the following package object:
package object app {
type FaultTolerantServiceGroup = Seq[Service]
object FaultTolerantServiceGroup{
def apply(svcs: Service*): FaultTolerantServiceGroup = Seq(svcs: _*)
}
class FaultTolerantServiceGroupOps(val F: FaultTolerantServiceGroup){
def hasFailed: Boolean = F.forall(_.failed())
}
trait FaultTolerantServiceGroupSyntax{
implicit def serviceGroup2Ops(F: FaultTolerantServiceGroup) = new FaultTolerantServiceGroupOps(F)
}
}
So I added the method hasFailed to FaultTolerantServiceGroup. But I'm not sure about this decision.
Would it be better to define a typeclass, say
trait Watchable[T]{
def hasFailed(t: T): Boolean
}
And implicitly provide an instance of Watchable[FaultTolerantServiceGroup]?
In my humble opinion implicit functions become much harder to read afterwards. Even when reading my old code it can sometimes be confusing when objects have methods that appear out of the blue.
I have yet to see an instance where implicits are easier to reason about than declarative functions:
val failedGroup : FaultTolerantServiceGroup => Boolean = _.forall(_.failed())
The resulting code doesn't seem any better, or worse, than implicits but at least it's obvious where functionality is coming from:
val group : FaultTolerantServiceGroup = ???
//no implicit
val failed = failedGroup(group)
//with implicits : how does a Seq have a hasFailed method?
val failed = group.hasFailed
Explicit functions also make Iterable functions easier to read:
val groups : Iterable[FaultTolerantServiceGroup] = ???
val failedGroups = groups filter failedGroup
I have the following code
import scala.concurrent.Future
class RequestType
class Read extends RequestType
class Write extends RequestType
object Main {
def main(args: Array[String]): Unit = {
}
def dbrequest[T <: RequestType](t: T): Future[T] = {
val dBRequest = new DBRequest
t match {
case r: Read => dBRequest.read(r)
case w: Write => dBRequest.write(w)
}
}
}
class DBRequest {
def read(r: Read): Future[Read] = {
Future(r)
}
def write(w: Write): Future[Write] = {
Future(w)
}
}
read and write method return a Future of type RequestType. If T is bounded and Future is covariant, then why is the compiler failing to conform type of Future[Read] or Future[Write] to Future[T]
Your code will compile with one small change.
def dbrequest[T <: RequestType](t: T): Future[RequestType] = {
So why is it that returning Future[RequestType] works and returning Future[T] doesn't, especially since T is bounded the way it is?
Think of it this way: T is resolved at compile time. With every invocation of dbrequest() the compiler turns T into either Read or Write. The match statement, on the other hand, is resolved at run time. So from the compiler's perspective the match statement returns both Read and Write.
As has been pointed out, you don't really need a type parameter in this code, as presented. The following simplification is equivalent.
def dbrequest(t: RequestType): Future[RequestType] = {
If T is bounded and Future is covariant, then why is the compiler failing to conform type of Future[Read] or Future[Write] to Future[T]
Your code would make sense if T was guaranteed to be Read or Write.
But it could Read with SomeTrait. Or a singleton type (so even making RequestType, Read and Write sealed wouldn't help). Etc. See my answer to Returning subclass of parameterized type in Scala for a solution (just replace Output in that question with Future).
Consider the following Akka actor. Implemententations of this actor should need to override and implement handleMessage(message: T). This implementation should call executeQueryLookup on its last line
abstract class BaseQueryLookupActor[T, U] extends Actor {
def receive = {
case message: T => handleMessage(message)
case message => unhandled(message)
}
def handleMessage(message: T) // Abstract method
def executeQueryLookup(cacheName: String, queryName: String, params: Map[String, String]) {
// Concrete method that contains query logic
}
}
I realize there are multiple ways to achieve this without using any Scala functional concepts, but I'm thinking that it is also possible to achieve this somehow using a partial function or currying.
If so, how would I achieve this?
i'm not sure i follow, but would it work to define
def handleMessage(message: T): (String, String, Map[String, String])
and call executeQueryLookup yourself in the base class, like
def receive = {
case message: T =>
val (cacheName, queryName, params) = handleMessage(message)
executeQueryLookup(cacheName, queryName, params)
case message => unhandled(message)
}
If you leave it to the subclass to implement a method, and you call that method, then there is nothing the superclass can do to enforce that the implementation in the subclass does some specific action. This has nothing to do with currying or partial functions.
You will have another problem with your actor, since the generic type T is erased on the JVM: at runtime T = java.lang.Object. The match will therefore always be successful and the second case will never be invoked. You will have to pass along type tags both when creating the actor and with every message. A crude approximation would be to use classTag instead, since every JVM object has a .getClass method, but that will only match the outermost type and it will not help if you want to distinguish a List[Int] from a List[String].
I'm writing a Play! 2.1 application using ReactiveMongo. each persistable case class has an object that holds 2 implicit objects, implementing BSONReader[...] and BSONWriter[...], and each case class has methods to return these:
trait Persistable {
implicit def getReader: BSONReader[Persistable]
implicit def getWriter: BSONWriter[Persistable]
val collectionName: String
}
case class MyObj () extends Persistable {
override val collectionName: String = MyObj.collectionName
override def getReader: BSONReader[MyObj] = MyObj.MyObjBSONReader
override def getWriter: BSONWriter[MyObj] = MyObj.MyObjBSONWriter
}
object MyObj{
val collectionName: String = "MyObj"
implicit object MyObjBSONReader extends BSONReader[MyObj] {
def fromBSON(document: BSONDocument): MyObj = {
val doc = document.toTraversable
new MyObj(
)
}
}
implicit object MyObjBSONWriter extends BSONWriter[MyObj] {
def toBSON(myObj: MyObj) = {
BSONDocument(
)
}
}
for some reason, getReader seems to work fine, but getWriter errors:
overriding method getWriter in trait Persistable of type =
reactivemongo.bson.handlers.BSONWriter[models.persistable.Persistable];
method getWriter has incompatible type
what am i doing wrong? both seem to have similar signatures.
another hint is that if i remove the return type from getWriter, i get complie time error in eclipse:
type mismatch; found : models.persistable.MyObj.MyObjBSONWriter.type required:
reactivemongo.bson.handlers.BSONWriter[models.persistable.Persistable]
UPDATE:
I did as #AndrzejDoyle said below, but then the implementation of Persister, which was the heart of this exercise, complains:
def insert(persistable: Persistable) = {
val collection = db(persistable.collectionName)
import play.api.libs.concurrent.Execution.Implicits._
implicit val reader = persistable.getReader
implicit val writer = persistable.getWriter
collection.insert(persistable)
}
error:
trait Persistable takes type
parameters
It is due to covariance and contravariance.
The mongodb reader is defined as BSONReader[+DocumentType]. The + in the generic parameter, means that this class is covariant in that parameter. Or more fully,
If B is a subclass of A, then BSONReader[B] is a subclass of BSONReader[A].
Therefore you can use a BSONReader[MyObj] everywhere that a BSONReader[Persistable] is required.
On the other hand, the writer is contravariant: BSONWriter[-DocumentType]. This means that
If B is a subclass of A, then BSONWriter[B] is a superclass of BSONWriter[A].
Therefore your BSONWriter[MyObj] is not a subclass of BSONWriter[Persistable], and so cannot be used in its place.
This might seem confusing initially (i.e. "why does contravariance make sense when it's 'backwards'?"). However if you think about what the classes are doing, it becomes clearer. The reader probably produces some instance of its generic parameter. A caller then might expect it to produce a Persistable - if you have a version that specifically produces MyObjs instead then this is fine.
The writer on the other hand, is probably given an object of its generic parameter. A caller with a BSONWriter[Persistable] will call the write() method, passing in an instance of Persistable to be written. Your implementation can only write instances of MyObj, and so it doesn't actually match the interface. On the other hand, a BSONWriter[Object] would be a subclass of any BSONWriter, since it can (from a type perspective) accept any type as an argument.
The fundamental problem seems to be that your Persistable trait is looser than you intended. You probably want each implementation to return a reader and writer parameterized on itself, rather than on Persistable in general. You can achieve this via self-bounded generics:
trait Persistable[T <: Persistable[T]] {
implicit def getReader: BSONReader[T]
implicit def getWriter: BSONWriter[T]
val collectionName: String
}
and then declare the class as MyObj[MyObj]. Now the reader and writer are expected to be parameterised on MyObj, and your existing implementations will compile.
I have asked a few questions around this topic but this time I want to make it a more general discussion, since it seems to me that Scala is lacking some very important blocks.
Consider the following code (which is simplified from my real project),
trait World {
type State <: StateIntf
def evolve(s: State): State
def initialState: State
}
class Algorithm(world: World) {
def process(s: world.State) {
val s1 = world.evolve(s)
// ... do something with s and s1
}
}
Everything seems so beautiful and mathematical, but
object SomeWorld extends World {...}
new Algorithm(SomeWorld).process(SomeWorld.initialState) // incompatible type
Certainly you can do in the following way
trait World {
type State <: StateIntf
var s: State
def evolve: Unit // s = next state
def initialize: Unit // s = initial state
def getState: StateIntf = s
}
But we are just back to the mutable world.
I am told that this is because Scala does not have flow analysis. If that is the problem, shouldn't Scala get that piece? I only need that compilor can be aware that values passed from val to val are the same so that their inner types must agree. This seems so natural to me, as:
val is the most foundamental concept that involves immutability in scala
Path dependent type compatability is needed to model things like World with complete immutability (which is highly desired from a mathematical perspective)
Flow analysis of passing vals solve the problem
Am I asking for too much? Or is there already a nice way of solving it?
I think that generics provides a simpler solution to this problem:
trait World[S <: StateInf] {
def evolve(s: S): S
def initialState: S
}
class Algorithm[S <: StateInf](world: World[S]) {
def process(s: S) {
val s1 = world.evolve(s)
// ... do something with s and s1
}
}
The compiler sometimes needs a little bit of help to prove that what you are doing is legal when using path dependent types. I.e., as you said, the compiler is missing flow analysis, so we must tell it explicitly that we aren't just using any World, we are using exactly SomeWorld so that we can use SomeWorld.initialState.
In your case, if you change Algorithm like so:
class Algorithm[W <: World](world: W) {
def process(s: world.State) {
val s1 = world.evolve(s)
// ... do something with s and s1
}
}
Then the following compiles:
object SomeWorld extends World {...}
new Algorithm[SomeWorld.type](SomeWorld).process(SomeWorld.initialState)