Skip to content

Instantly share code, notes, and snippets.

@cb372
Created May 28, 2018 20:25
Show Gist options
  • Save cb372/c13f282dc5d3439bba1e7918647fd314 to your computer and use it in GitHub Desktop.
Save cb372/c13f282dc5d3439bba1e7918647fd314 to your computer and use it in GitHub Desktop.

First of all, sorry for the radio silence. I forgot this channel existed, and Gitter notifications have always been flaky for me.

Second, it sounds like the reasons behind adding a type parameter to Cache are not very clear, so I'll explain below. Maybe I should add some kind of design/architecture page to the microsite?

Third, thank you for all your work on the PR. Sorry again for not being available to give feedback earlier.

About Cache vs Cache[V]

The thinking behind turning it into Cache[V] was mostly about improving type safety.

In the old API, because a Cache would accept all types, it was all too easy to write a Foo and then read it back as a Bar, resulting in runtime exceptions.

In the new API, you can think of Cache[V] as a type class meaning "it is possible to cache a value of type V. If you can find an instance of the Cache[V] type class, you can cache your value of type V. If not, you are trying to cache something that has the wrong type.

This works really well for the common case where you are only trying to cache things of one type, e.g. User.

It also works well for the case where you maintain caches for a few different types, say User and Product. In that case, you could still make the mistake of writing a value as a User and then trying to read it back as a Product, but you are protected from the class of errors that involves any other arbitrary types, e.g. writing a String and then trying to read it as a Product.

In the case where you don't know the types beforehand, or rather you want your cache to be able to store any arbitrary types, it's still possible to "opt out" of this type safety, by having a Cache[Any]. It's just that you need to do the nasty unsafe casting in userland instead of having it hidden away inside the library like it used to be.

You could also write it like this, although it still involves casting. There's no getting around that.

scala> import com.github.benmanes.caffeine.cache._
import com.github.benmanes.caffeine.cache._

scala> import scalacache._
import scalacache._

scala> import scalacache.caffeine._
import scalacache.caffeine._

scala> val underlying = Caffeine.newBuilder().build[String, Entry[Any]]()
underlying: com.github.benmanes.caffeine.cache.Cache[String,scalacache.Entry[Any]] = com.github.benmanes.caffeine.cache.UnboundedLocalCache$UnboundedLocalManualCache@672debf8

scala> implicit def caffeineCache[V](implicit config: CacheConfig): scalacache.Cache[V] = CaffeineCache(underlying.asInstanceOf[com.github.benmanes.caffeine.cache.Cache[String, E
ntry[V]]])
caffeineCache: [V](implicit config: scalacache.CacheConfig)scalacache.Cache[V]

scala> import scalacache.modes.sync._
import scalacache.modes.sync._

scala> sync.put("hello")("world", ttl = None)
res1: Any = ()

scala> sync.get[String]("hello").get
res2: String = world

scala> sync.put("hi")(123, ttl = None)
res3: Any = ()

scala> sync.get[Int]("hi").get
res4: Int = 123

Corollory: why not Cache[F[_]]?

I don't think there's a good reason to restrict a given Cache[V] to only support operations in some fixed effect F[_].

That's why I put the F[_] on the methods, not on the class. It just provides a bit more flexibility, e.g. to use different effects in different parts of your program.

About cats-effect

I've always tried to keep the number of dependencies as low as possible, and adding a dependency on cats-effect would be a huge step that I'm not too keen on. For a start, some users with Scalaz dependencies don't want to bring in a transitive Cats dependency, and vice versa. The strategy for ScalaCache has always been to keep the core almost dependency-free and have separate modules for Cats, Scalaz, Circe, etc.

Of course, if I were copying huge swathes of cats-effect into my own library just for the sake of avoiding a dependency, that would be crazy, but at the moment that is not the case. It's just a few simple type classes.

There is another reason why I wrote my own versions of the cats-effect type class hierarchy. It's actually subtly different in one place: handleNonFatal in MonadError takes its argument lazily. This is so we can do some shady unlawful stuff to provide an instance of MonadError (and Async) for Id. Cats and cats-effect don't do this because in general it doesn't make any sense, but it works for ScalaCache's limited use case.

About scodec

As above, I would be wary of adding a dependency like this to core unless it is justified.

@guizmaii
Copy link

guizmaii commented Jun 10, 2018

My answers:

  1. Scodec was a bad idea.
  2. Your arguments against adding dependencies make sens. Let's continue without for now.
  3. About Cache vs Cache[V], I think that we can propose all the possible alternatives to the users:
  • Cache[F[_]: Async, V]
  • Cache[F[_]: Async]
  • Cache[V]
  • Cache

I'll try to implement these variants in my PR.

I understand the arguments about typesafety of Cache[V] but when V could be a lot of things, it generates too much boilerplate.

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