-
Notifications
You must be signed in to change notification settings - Fork 120
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
Bring Your Own Effect #145
Conversation
It wasn't running on most of the code in the core module. Since this PR is probably going to touch every file in the codebase, why not throw some reformatting into the mix?
It's sometimes convenient to just get a plain old value back instead of wrapping it in any monad. This mode allows you to do this even with async cache impls e.g. memcached.
No need to parameterise CacheAlg and Mode on Sync/Async
Non-compiling tests are commented out for now
Because of changes in the API, we can now hardcode the representation to Array[Byte]
⛰ 🗻 I was just discussing what I'd like to do with Scanamo and @LATaylor-guardian pointed out you'd just done the equivalent here. I might take some notes. |
@LATaylor-guardian stop stalking me! @philwills happy to talk you through it tonight over a 🍺 at the ⭐️ of 👑 |
I haven't developed in Scala in 5 or 6 years, so apologies that I cannot provide feedback as an active user. My main concerns are,
I think all of these can be remedied. |
the only comment I'd have from reading your very clear description is that |
} | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cb372 what do you think of putting this in a micro library? with the monix
, cats
, scalaz
implementation as module. This way I could reuse that in the scala guardian client library and @philwills could as well in scanamo.
This is the kind of code I don't want to copy/paste and maintain in multiple librairies.
It's two years since I left a comment about how I should put my clever trousers on, and a whole month since I started this branch, but it's finally here and ready for review*.
*I ended up touching nearly every file in the project, so I don't expect anyone to actually review the diff!
The headline
Bring Your Own Effect.
In other words, ScalaCache's API is now parameterised by a higher-kinded type
F[_]
, which is the effect monad in which the computation is wrapped. You, the user, can decide whether you want ScalaCache to return aFuture
, aTry
, a ScalazTask
, a plain old value, or whatever else your heart desires.Wherever a function previously looked like
or
, it now looks like
Modes
The effect
F[_]
is decided by a "mode", which is passed implicitly.Until now ScalaCache has supported both synchronous blocking execution and Scala
Future
, albeit in a rather ad-hoc and inconsistent fashion. In order to continue the support for these two execution styles, ScalaCache comes with a few modes out of the box:Sync
For simple, blocking execution on the current thread, throwing exceptions in the case of errors, import the sync mode:
Scala Future
To wrap everything in a
Future
, import the appropriate mode, along with an execution context:Try
A mode to wrap things in
Try
is also provided out of the box:More exotic modes: cats-effect, Monix, Scalaz
There are also modes for cats-effect IO, Monix Task and Scalaz Task, each provided in separate modules.
e.g.
These differ from the aforementioned modes in that they are lazy: they defer the computation until they are specifically told to run.
Note: ScalaCache core is politically neutral, having no dependencies on either Cats or Scalaz.
Safer, DRYer, more sensible API
Until now ScalaCache has made type safety optional. It had two different APIs: a type-safe one and a more loosely typed one. Using the latter, it is possible to write a value to the cache as a
Foo
and then try to read it back out as aBar
, leading to a runtime error.As I've grown older and wiser, I've come to appreciate that this was a poor design decision. The existence of two different, but very similar, APIs, was inelegant, confusing for users and annoying to maintain.
In this PR, I've got rid of the loosely typed API, meaning ScalaCache can only be used in one way. Less confusion, less duplication.
As before, the intended usage of ScalaCache is via the API exposed in the package object:
But now, instead of a
ScalaCache
instance, you will need aCache
instance in scope.Cache
is parameterised by the type of the values it stores:This means that you can only store values of type
Foo
in that cache.You'll also need a mode in scope:
Then you'll be able to call the methods on the package object in a type-safe way:
Codec
In the existing ScalaCache API, a serialization codec is passed implicitly into every function where it is needed. But this means it is possible to e.g. write a value to the cache with one codec and then try to read it back with another, leading to a runtime error.
Just as a cache should be parameterised by the type of values it stores, it also makes more sense to pass in the codec just once, when the cache is constructed. The new API design allows us to do that.
Example usage
Here is an example of using ScalaCache with Caffeine and Scalaz Task.
Sync API
As part of this work I really wanted to get rid of the "sync" API, which is a copy of the API that returns plain old values not wrapped in any effect.
Unfortunately Scala's type inference didn't want to cooperate, so the sync API is still there. All it does is call the normal API, setting
F[_]
toId[_]
.Example usage:
Similarly,
memoizeSync
(the sync version ofmemoize
) is still there.Other breaking changes
As I was already substantially changing the API, I took the opportunity to clean up a few years' worth of accumulated cruft:
cachingWithTtl
and the overloads ofmemoize
are gone.Codec
: the underlying representation type is now hardcoded toArray[Byte]]
Benchmarks
TODO: run the benchmarks on this branch and on master, and post the results here.
Versioning and roadmap
The current version of ScalaCache is 0.10.x.
Assuming there isn't massive resistance to this PR and it actually gets merged, it will be released as 0.20.x. The version jump is to represent the huge number of breaking changes in the PR.
After that I will start working towards a 1.0 release. Things I would like to tackle before then:
I will still maintain the 0.10.x branch for as long as people need it (within reason).
Still TODO on this PR