I'm new in scala programming. I'm puzzled now for how to declare a biz method in a asynchronous and functional way, that the method implementation should include many log message. As a bad practice, I write the code like this :
// trait
trait StoreService {
def create[Config]: Kleisli[Future, Config, Store]
}
// and the interpreter
trait StoreServiceInterpreter extends StoreService {
def create[Config]: Kleisli[Future, Config, Store] = Kleisli {cfg =>
// some implementation ...
log.info("bla bla bla ...")
// some implementation ...
// return a store
Store(...)
}
}
It's bad, cause the implementation is with side-effect, log something int the place. So, I change the method declaration like this:
// trait
trait StoreService {
def create[Config]: Kleisli[Future, Config, Writer[Vector[String], Store]]
}
// and the interpreter
trait StoreServiceInterpreter extends StoreService {
def create[Config]: Kleisli[Future, Config, Writer[Vector[String], Store]] = Kleisli {cfg =>
// some implementation ...
// log.info("bla bla bla ...")
// some implementation ...
// return a store
Writer(Vector("bla bla bla...", Store(...))
}
}
Using the Writer, the side-effect is eliminated, but the code is not clear:
Writer[Vector[String], Store]
has more noises than Store
, has any way to avoid the boilerplate code and remain the no-side-effect?log
is not ad-hoc! I should build a vector of String to hold the message, using :+
or ++
operation to add log. I think it's not ad-hoc logging, just like write log.info(...)
anywhere.Most of Scala developers I know tend to consider logging as "non-side-effect", for convenience. However, if you really want to track them, you might want to take a look on "free monad" concept. Further information: general description, example with logging.
Mine rough explanation is "let's model our program as some AST and interpret it". So, in AST you define a concept of "logging" but not implementation, that comes later, in interpretation. This approach allows you to keep an eye on logging and change the action (from write to /dev/null to async post to external service) without affecting the "business" part of your code.