{"id":21948134,"url":"https://github.com/evolution-gaming/cats-helper","last_synced_at":"2025-10-29T23:31:40.656Z","repository":{"id":39924228,"uuid":"174219847","full_name":"evolution-gaming/cats-helper","owner":"evolution-gaming","description":"Helpers for cats \u0026 cats-effect","archived":false,"fork":false,"pushed_at":"2024-08-12T13:52:48.000Z","size":500,"stargazers_count":49,"open_issues_count":41,"forks_count":17,"subscribers_count":11,"default_branch":"master","last_synced_at":"2024-08-13T11:03:39.814Z","etag":null,"topics":["cats","cats-effect","scala","tagless-final"],"latest_commit_sha":null,"homepage":null,"language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/evolution-gaming.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-03-06T20:57:03.000Z","updated_at":"2024-08-13T11:03:39.814Z","dependencies_parsed_at":"2024-01-17T12:57:42.978Z","dependency_job_id":"0c4ba90f-3e39-49b4-b54b-30d23bd1c653","html_url":"https://github.com/evolution-gaming/cats-helper","commit_stats":null,"previous_names":[],"tags_count":113,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evolution-gaming%2Fcats-helper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evolution-gaming%2Fcats-helper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evolution-gaming%2Fcats-helper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evolution-gaming%2Fcats-helper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/evolution-gaming","download_url":"https://codeload.github.com/evolution-gaming/cats-helper/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238909484,"owners_count":19550845,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cats","cats-effect","scala","tagless-final"],"created_at":"2024-11-29T05:12:09.078Z","updated_at":"2025-10-29T23:31:40.642Z","avatar_url":"https://github.com/evolution-gaming.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Cats Helper\n[![Build Status](https://github.com/evolution-gaming/cats-helper/workflows/CI/badge.svg)](https://github.com/evolution-gaming/cats-helper/actions?query=workflow%3ACI)\n[![Coverage Status](https://coveralls.io/repos/evolution-gaming/cats-helper/badge.svg)](https://coveralls.io/r/evolution-gaming/cats-helper)\n[![Codacy Badge](https://app.codacy.com/project/badge/Grade/31cabcf524d648c9b36b27697f4721ef)](https://app.codacy.com/gh/evolution-gaming/cats-helper/dashboard?utm_source=gh\u0026utm_medium=referral\u0026utm_content=\u0026utm_campaign=Badge_grade)\n[![Version](https://img.shields.io/badge/version-click-blue)](https://evolution.jfrog.io/artifactory/api/search/latestVersion?g=com.evolutiongaming\u0026a=cats-helper_2.13\u0026repos=public)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellowgreen.svg)](https://opensource.org/licenses/MIT)\n\n## ClockHelper\n\n```scala\nimport com.evolutiongaming.catshelper.ClockHelper._\n\nval clock = Clock.const[Id](nanos = 1000, millis = 2)\n\nclock.millis // 2\nclock.nanos // 1000\nclock.micros // 1\nclock.instant // Instant.ofEpochMilli(2)\n```\n\n## MeasureDuration\n\nProvides a way to measure duration of a computation in a pure way.\n\nExample:\n```scala\nimport com.evolutiongaming.catshelper.MeasureDuration\n\nfor {\n  duration \u003c- MeasureDuration[IO].start\n  _        \u003c- doSomething\n  duration \u003c- duration\n} yield duration\n```\n\nSyntax extensions are also available, allowing to measure duration of a computation and execute an effect with it:\n```scala\nimport com.evolutiongaming.catshelper.syntax.measureDuration._\n\nfor {\n  int1 \u003c- IO.pure(1).measured(elapsed =\u003e IO.println(s\"elapsed: $elapsed\"))\n  int2 \u003c- IO.pure(1).measuredCase(\n    successF = elapsed =\u003e IO.println(s\"Succeeded: $elapsed\"),\n    failureF = elapsed =\u003e IO.println(s\"Failed: $elapsed\")\n  )\n} yield int1 + int2\n```\n\n## SerialRef\n\nLike [`Ref`](https://typelevel.org/cats-effect/concurrency/ref.html) but allows `A =\u003e F[A]` rather than `A =\u003e A`  \nEnsures that updates are run serially\n\n```scala\nimport com.evolutiongaming.catshelper.SerialRef\n\nfor {\n  ref \u003c- SerialRef.of[IO, Int](0)\n  _   \u003c- ref.update(a =\u003e (a + 1).pure[IO])\n} yield {}\n```\n\n## LazyVal\n\nFunctional alternative to `lazy` keyword in Scala\n\n```scala\ntrait LazyVal[F[_], A] {\n\n  def get: F[A]\n\n  def getLoaded: F[Option[A]]\n}\n```\n\n## ToFuture \u0026 FromFuture\n\n```scala\ntrait ToFuture[F[_]] {\n  def apply[A](fa: F[A]): Future[A]\n}\n\ntrait FromFuture[F[_]] {\n  def apply[A](future: =\u003e Future[A]): F[A]\n}\n```\n\n## ToTry \u0026 FromTry\n\n```scala\ntrait ToTry[F[_]] {\n\n  def apply[A](fa: F[A]): Try[A]\n}\n\ntrait FromTry[F[_]] {\n\n  def apply[A](fa: Try[A]): F[A]\n}\n```\n\n## Log\n\n```scala\ntrait Log[F[_]] {\n\n  def debug(msg: =\u003e String): F[Unit]\n\n  def info(msg: =\u003e String): F[Unit]\n\n  def warn(msg: =\u003e String): F[Unit]\n\n  def warn(msg: =\u003e String, cause: Throwable): F[Unit]\n\n  def error(msg: =\u003e String): F[Unit]\n\n  def error(msg: =\u003e String, cause: Throwable): F[Unit]\n}\n```\n\n## Runtime\n\n```scala\ntrait Runtime[F[_]] {\n\n  def availableCores: F[Int]\n\n  def freeMemory: F[Long]\n\n  def totalMemory: F[Long]\n\n  def maxMemory: F[Long]\n\n  def gc: F[Unit]\n}\n```\n\n## ThreadLocalRef\n\n```scala\ntrait ThreadLocalRef[F[_], A] {\n\n  def get: F[A]\n\n  def set(a: A): F[Unit]\n\n  def update(f: A =\u003e A): F[Unit]\n\n  def modify[B](f: A =\u003e (A, B)): F[B]\n}\n```\n\n## ResourceFenced\n\nThis is useful to ensure `release` called at most once, in cases when \"unsafe\" api like `Resource.allocated` being used\n\n```scala\nval resource: Resource[F, A] = ???\nresource.fenced\n```\n\n## ReadWriteRef\n\nA mutable reference to `A` value with read-write lock semantics.\n\n## FeatureToggled\n\nManages a given `Resource[F, A]` providing access to it only when a feature-toggle is on.\n\n```scala\nval serviceResource: Resource[F, AService] = ???\nval flag: F[Boolean] = ???\n\nval ftService: Resource[F, Resource[F, Option[AService]]] = FeatureToggled\n  .polling(\n    serviceResource,\n    flag,\n    pollInterval = 10.seconds,\n    gracePeriod = 30.seconds,\n  )\n\nftService.use { access =\u003e\n  access.use {\n    case Some(service) =\u003e service.doStuff(…)\n    case None          =\u003e F.unit\n  }\n}\n```\n\n## Logback module\n\n### Separate module\n\nThe logback module lives in a separate `cats-helper-logback' module to avoid dependency on a logback in case the user chooses a different logging backend. This is important to avoid the problem of multiple bindings when mapping the logging framework with SLF4J.\n\n### LogOfFromLogback\n\n#### Motivation\nDirect logback usage required to overcome limitations of SLF4J MDC API.\nSLF4J MDC API heavily rely on [[ThreadLocal]], example: ch.qos.logback.classic.util.LogbackMDCAdapter\nLogback' [[LoggingEvent]] allow setting MDC directly as Java map that should have performance benefits compared with SLF4J/Logback implementation.\n\n#### CAUTION!\nPlease be aware that using other version of logback (than used in `cats-helper-logback`) might bring '''RUNTIME ERRORS''' or '''MISSING LOGS''' in case of binary incompatibility between them.\nSuggested approach is in using exactly same logback version as used in `cats-helper-logback` (among all others available through transitive dependencies)\n\n#### SLF4J compatibility\nIn some cases it may be necessary to allocate the logback instance manually as well as using the SLF4J API in the end user code. However, if multiple LoggerContexts are instantiated at the same time, this could lead to unexpected behaviour, such as the RollingFileAppender writing to multiple files instead of one.\nTo cover such cases, the internal implementation of `LogOfFromLogback` uses the SLF4J API to instantiate the logback context, so that later use of the SLF4J API will pick up the same context instance created by `LogOfFromLogback`.\n\n## PureTest\n\nThis helper lives in a separate `cats-helper-testkit` module. It is makes testing `F[_]`-based code easier.\n\n**NOTE:** `cats-helper-testkit` is an experimental module and may break SemVer guarantees from time to time.\nHowever we will do our best to avoid unnecessary breakages.\n\n```scala\n\"what time is it now?\" in PureTest[IO].of { env =\u003e\n  import env._\n  for {\n    _ \u003c- IO.sleep(1.hour)\n    _ \u003c- testRuntime.getTimeSinceStart.map(_ shouldBe 1.hour)\n  } yield ()\n}\n```\n\n## Setup\n\n```scala\naddSbtPlugin(\"com.evolution\" % \"sbt-artifactory-plugin\" % \"0.0.2\")\n\nlibraryDependencies += \"com.evolutiongaming\" %% \"cats-helper\" % \"2.2.3\"\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fevolution-gaming%2Fcats-helper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fevolution-gaming%2Fcats-helper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fevolution-gaming%2Fcats-helper/lists"}