cats-effect icon indicating copy to clipboard operation
cats-effect copied to clipboard

The Random docs skip over the hardest part ..and other doubts

Open benhutchison opened this issue 2 years ago • 4 comments

  • The docs for std.Random do not include a working example; ie if you concat the snippets on the page, it will not compile. And on closer inspection, you'll discover that the missing part is omitted from the docs not because it's too trivial to mention, but rather because it's the most difficult piece!

The problem lies in the section "Creating a Random instance".

Obtaining an instance of Random can be as simple as:

import cats.effect.IO
import cats.effect.std.Random

Random.scalaUtilRandom[IO]

Unfortunately, the user will discover that creating a random instance is not so simple at all. The expression given is of type IO[Random[IO]], so that's not actually an instance.

In fact, the user will likely need to execute that expression within the program and then hoist the result into given/implicit scope, for later sections of the program to use. This is a slightly non-typical way to create instances and I couldn't find any such example in the docs.

  • Secondly, the use of the word "instance" here is problematic. It has range of meanings. For people of Haskell persuasion, to call something that's created during the running program for a specific lifetime an "instance" would confuse them.

  • And this poses another library design question: why is Random an instance, that is, passed implicitly? Other objects in std like Ref and Queue are passed explicitly, but Random is passed implicitly. Is there a clear principle that specifies why a particular thing is passed explicitly vs implicitly?

benhutchison avatar Apr 01 '23 22:04 benhutchison

The expression given is of type IO[Random[IO]], so that's not actually an instance.

In fact, the user will likely need to execute that expression within the program

Yeah, this is a recurring shortcoming of our documentation, which I attempted to cover in https://github.com/typelevel/cats-effect/issues/3132. Basically, how to work with mutable state. Essentially you should treat it the same way as a Ref, which is also allocated as an IO[Ref[A]]


but Random is passed implicitly. Is there a clear principle that specifies why a particular thing is passed explicitly vs implicitly?

This is also an excellent, recurring question 😁 one good rule of thumb is that unless there is a single, canonical instance, it probably should not be treated implicitly. So we might want to reconsider this one.

armanbilge avatar Apr 01 '23 23:04 armanbilge

The expression given is of type IO[Random[IO]], so that's not actually an instance. In fact, the user will likely need to execute that expression within the program

Yeah, this is a recurring shortcoming of our documentation, which I attempted to cover in #3132. Basically, how to work with mutable state. Essentially you should treat it the same way as a Ref, which is also allocated as an IO[Ref[A]]

but Random is passed implicitly. Is there a clear principle that specifies why a particular thing is passed explicitly vs implicitly?

This is also an excellent, recurring question 😁 one good rule of thumb is that unless there is a single, canonical instance, it probably should not be treated implicitly. So we might want to reconsider this one.

That rule of thumb makes sense to me. And to first approximation, Random IMO should be an instance by that definition but Ref and Queue not. In rare cases, one might want different randoms, but it seems far different case to the other two.

Would a page entitled eg "Design FAQ" be a suitable place to accumulate notes about these sort of principles? While FAQs aren't the most elegant form of docs, they can grow incrementally and in patch-work fashion, and don't over-promise that all aspects of the design would be mentioned.

benhutchison avatar Apr 02 '23 02:04 benhutchison

I have the feeling that a Random[F] is somewhere between a "proper" typeclass instance (e.g., a Monad[F]) and a "not typeclass instance at all" (e.g., a Ref[F, A]). There is no single canonical instance (unlike as for Monad), but it is also not likely that in the same application multiple Randoms are needed (unlike as for Ref).

The current code seems to reflect this strange situation: as far as I can tell there is no implicit Random[F] available, unless there is already an implicit Random[G] available (e.g., implicits are automatically derived for monad transformers).

Whether Random[F] should be passed implicitly: hard to say. I'd say probably not, as it has mutable state; but I'm not entirely certain.

(Just a note on terminology: as far as I know, instance in Scala can refer to things other than typeclass instances, e.g., a "String instance" is just an object for which it's type is String. This is just the OO terminology.)

durban avatar Apr 10 '23 09:04 durban

I've had a use case for multiple randoms. I needed a SecureRandom for some locations but in the rest of the codebase it's preferable to use plain Random for the benefit to performance. It's not a necessary use case but I'd really dislike to lose the ability to do that easily.

Daenyth avatar Sep 19 '23 17:09 Daenyth