Last active
September 16, 2022 11:26
-
-
Save tvoklov/c773c6ce93425cc61ef3759cebbfb9c2 to your computer and use it in GitHub Desktop.
nanoid generator in scala using cats effect
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import cats.Monad | |
import cats.effect.kernel.Async | |
import cats.effect.std.Random | |
import cats.syntax.all._ | |
def generate[F[_]: Monad](random: Int => F[List[Byte]], alphabet: Array[Char], size: Int) = { | |
val mask = (2 << (Math.log(alphabet.length - 1) / Math.log(2)).toInt) - 1 | |
val step = Math.ceil(1.6 * mask * size / alphabet.length) | |
def go(filled: List[Char], rest: List[Byte]): F[List[Char]] = | |
if (filled.size == size) Monad[F].pure(filled.reverse) | |
else | |
rest match { | |
case Nil => random(step.toInt).flatMap(go(filled, _)) | |
case x :: xs => | |
alphabet.lift(x & mask) match { | |
case None => go(filled, xs) | |
case Some(v) => go(v :: filled, xs) | |
} | |
} | |
go(Nil, Nil) | |
} | |
val defaultNanoIdAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict".toCharArray | |
val defaultNanoIdSize = 21 | |
def nanoid[F[_]: Async]: F[String] = | |
for { | |
r <- Random.scalaUtilRandom | |
id <- generate( | |
i => r.nextBytes(i).map(_.toList), | |
defaultNanoIdAlphabet, | |
defaultNanoIdSize | |
) | |
} yield id |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import cats.effect.std.Random | |
import cats.effect.unsafe.implicits.global | |
import cats.effect.IO | |
import cats.effect.implicits._ | |
import cats.syntax.all._ | |
val justAnId = nanoid[IO].unsafeRunSync() | |
// with every run, this will be different | |
// val justAnId: List[Char] = List(d, b, a, b, c) | |
val `sameSeed=sameId` = { | |
val seed = 2 | |
val size = 5 | |
val alphabet = "abcdef" | |
val idGen = | |
for { | |
random <- Random.scalaUtilRandomSeedInt[IO](seed) | |
id <- | |
generate( | |
i => random.nextBytes(i).map(_.toList), | |
alphabet.toCharArray, | |
size | |
) | |
} yield id | |
LazyList | |
.continually(idGen) | |
.take(5) | |
.toList | |
.parUnorderedTraverse(identity) | |
}.unsafeRunSync() | |
// val `sameSeed=sameId`: List[List[Char]] = | |
// List( | |
// List(b, c, e, d, a), | |
// List(b, c, e, d, a), | |
// List(b, c, e, d, a), | |
// List(b, c, e, d, a), | |
// List(b, c, e, d, a) | |
// ) | |
val `reusing-random` = { | |
val seed = 2 | |
val size = 5 | |
val alphabet = "abcdef" | |
// unfortunately, there seems to be no standard data type in cats effect that | |
// allows for truly functional randomness, i.e. where .next() functions return | |
// both the value AND the new random with the updated seed. | |
// | |
// if there was one, then you could have removed the F[_] constraint on the nanoid's `random` argument | |
for { | |
random <- Random.scalaUtilRandomSeedInt[IO](seed) | |
ids <- | |
LazyList | |
.continually( | |
(f: Int) => | |
random | |
.nextBytes(f) | |
.map(_.toList) | |
) | |
.take(5) | |
.toList | |
.traverse( | |
generate(_, alphabet.toCharArray, size) | |
) | |
} yield ids | |
}.unsafeRunSync() | |
// val reusing-random: List[List[Char]] = | |
// List( | |
// List(b, c, e, d, a), | |
// List(a, b, c, d, f), | |
// List(e, c, e, d, f), | |
// List(b, f, b, e, e), | |
// List(e, a, c, e, e) | |
// ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment