{"id":20768145,"url":"https://github.com/softwaremill/scala-common","last_synced_at":"2025-04-04T06:07:13.961Z","repository":{"id":2563835,"uuid":"46819610","full_name":"softwaremill/scala-common","owner":"softwaremill","description":"Tiny independent libraries with a single purpose, often a single class","archived":false,"fork":false,"pushed_at":"2025-02-27T00:23:41.000Z","size":208,"stargazers_count":120,"open_issues_count":12,"forks_count":16,"subscribers_count":33,"default_branch":"master","last_synced_at":"2025-03-28T05:11:10.867Z","etag":null,"topics":["benchmarking","commonlib","id-generation","scala","typeclasses"],"latest_commit_sha":null,"homepage":"https://softwaremill.com/open-source/","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/softwaremill.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":"2015-11-24T21:26:53.000Z","updated_at":"2024-09-16T08:02:40.000Z","dependencies_parsed_at":"2023-12-19T12:13:13.290Z","dependency_job_id":"db0b33a4-4396-49bc-a5e7-1dcaec78dceb","html_url":"https://github.com/softwaremill/scala-common","commit_stats":{"total_commits":210,"total_committers":23,"mean_commits":9.130434782608695,"dds":0.5714285714285714,"last_synced_commit":"aa70f1c04f3e385e2c156d85172d6dfcba579262"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/softwaremill%2Fscala-common","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/softwaremill%2Fscala-common/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/softwaremill%2Fscala-common/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/softwaremill%2Fscala-common/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/softwaremill","download_url":"https://codeload.github.com/softwaremill/scala-common/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247128747,"owners_count":20888235,"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":["benchmarking","commonlib","id-generation","scala","typeclasses"],"created_at":"2024-11-17T11:35:53.984Z","updated_at":"2025-04-04T06:07:13.944Z","avatar_url":"https://github.com/softwaremill.png","language":"Scala","readme":"# scala-common\n\n[![Join the chat at https://gitter.im/softwaremill/scala-common](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/softwaremill/scala-common?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n[![CI](https://github.com/softwaremill/scala-common/workflows/CI/badge.svg)](https://github.com/softwaremill/scala-common/actions?query=workflow%3ACI+branch%3Amaster)\n\nTiny independent libraries with a single purpose, often a single class. Available for Scala 2.12, 2.13, 3; JVM and JS.\n\n## Tagging\n\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.common/tagging_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.common/tagging_2.13)\n\nTag instances with arbitrary types. Useful if you'd like to differentiate between instances on the type level without\nruntime overhead. Tags are only used at compile-time to provide additional type safety.\n\nAn instance of type `T` tagged with type `U` has type `T @@ U` (which is sugar for `@@[T, U]`). You can only use\n`x: T @@ U` when an instance of type `T @@ V` is expected when `U` is a subtype of `V` (`U \u003c: V`).\n\nThe tag can be any type, but usually it is just an empty marker trait.\n\nTo add tags to existing instances, you can use the `taggedWith[_]` method, which returns a tagged instance \n(`T.taggedWith[U]: T @@ U`). Tagged instances can be used as regular ones, without any constraints.\n \nSBT dependency:\n\n````scala\nlibraryDependencies += \"com.softwaremill.common\" %% \"tagging\" % \"2.3.5\"\n````\n\nExample:\n\n````scala\nimport com.softwaremill.tagging._\n\nclass Berry()\n\ntrait Black\ntrait Blue\n\nval berry = new Berry()\nval blackBerry: Berry @@ Black = berry.taggedWith[Black]\nval blueBerry: Berry @@ Blue = berry.taggedWith[Blue]\n\n// compile error: val anotherBlackBerry: Berry @@ Black = blueBerry\n````\n\nOriginal idea by [Miles Sabin](https://gist.github.com/milessabin/89c9b47a91017973a35f).\nSimilar implementations are also available in [Shapeless](https://github.com/milessabin/shapeless) \nand [Scalaz](https://github.com/scalaz/scalaz).\n\n#### Tagging and typeclasses\nLet's consider the following example:\n\n```scala\nimport com.softwaremill.tagging._\n\n// Our typeclass\ntrait Serializer[T] {\n  def doSerialize(t: T): String\n}\n\n// Method that leverages typeclass, to transform some T into a String\ndef serialize[T](t: T)(implicit ser: Serializer[T]): String = {\n  ser.doSerialize(t)\n}\n\n// Typeclass instance for type `Long`\nimplicit val longSerializer = new Serializer[Long] {\n  override def doSerialize(t: Long): String = \"Long number: \" + t\n}\n\nval longNumber = 30L\nserialize(longNumber) // Compiles and returns \"Long number: 30\"\n\n// Our marker trait to be used as a tag\ntrait UserId\n\nval id: Long @@ UserId = 1024L.taggedWith[UserId]\nserialize(id) // Won't compile: could not find implicit value for parameter ser\n```\nBecause tagged type `T @@ U` is considered by the compiler as a different type than `T`, it will complain about missing implicit typeclass `Serializer[_]` for `T @@ U`, even if there is instance of `Serializer[T]` already in scope.\n\nTo solve this problem just either mix-in `TypeclassTaggingCompat[M[_]]`/`AnyTypeclassTaggingCompat` trait or import contents of the `AnyTypeclassTaggingCompat` object:\n```scala\nimport com.softwaremill.tagging.AnyTypeclassTaggingCompat._\n\nserialize(id) // Compiles and returns \"Long number: 1024\"\n``` \n\n`TypeclassTaggingCompat` brings implicit conversion, that can adapt any implicit `M[T]` to be used as `M[T @@ U]`.\n\n## Future Try extensions\n\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.common/futuretry_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.common/futuretry_2.13)\n\nProvides two utility methods for extending `Future`:\n\n - `tried: Future[Try[T]]` - reifying the Future's result.\n - `transformTry(f: Try[T] =\u003e Try[S]): Future[S]` - corresponds to 2.12's new `transform` variant, allowing to supply a single function (if, for example, you already have one handy), \n instead of two. _Note: unfortunately, it was not possible to name this method transform, due to how scalac handles implicit resolution._\n\nSBT depedency:\n\n````scala\nlibraryDependencies += \"com.softwaremill.common\" %% \"futuretry\" % \"1.0.1\"\n````\n\nExample:\n\n````scala\n\nval myFuture: Future[Foo] = ...\nval myUsefulTransformer: Try[Foo] =\u003e Try[Bar] = ...\n\ndef someWeirdApiMethod(future: Future[Try[Foo]])\n \nimport com.softwaremill.futuretry._\n\nsomeWeirdApiMethod(myFuture.tried)\n\nval myBetterFuture: Future[Bar] = myFuture.transformTry(myUsefulTransformer)\n````\n\n## Future Squash\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.common/futuresquash_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.common/futuresquash_2.13)\n\n### Goal\n\nMonad stacks are not easy to compose (e.g. in a for comprehension), and if you don't want to use Monad transformers you can use this Future additional methods.\n \n### FutureSquash.fromEither\n\n```scala\nimport com.softwaremill.futuresquash.FutureSquash\n\nFutureSquash.fromEither(Right(\"a\")) //Future(\"a\")\nFutureSquash.fromEither(Left(BoomError)) //Future(BoomError)\n```\n\n### squash Future[Either[Throwable, A]] and Future[Try[A]]\n\nYou can use `squash` on a `Future[Either[Throwable, A]]` to get a `Future[A]`.\n\n```scala\nimport FutureSquash._\n\nabstract class Error(message: String) extends Exception(message)\ncase object BoomError extends Error(\"Boom\")\n\nval fea: Future[Either[Error, String]] = Future(Right(\"a\"))\nval feb: Future[Either[Error, String]] = Future(Left(BoomError))\n\nfea.squash //Future(\"a\")\nfeb.squash //Future(BoomError)\n```\n\nYou can also `squash` on a `Future[Try[A]]` to get a `Future[A]` in much the same way:\n\n```scala\nimport FutureSquash._\n\nval fta: Future[Try[String]] = Future(Success(\"a\"))\nval ftb: Future[Try[String]] = Future(Failure(new Exception(\"Boom\")))\n\nfta.squash //Future(\"a\")\nftb.squash //Future(Exception(\"Boom\"))\n```\n\nIt can also be useful to compose several `Future[Either[Throwable, _]]` without monad transformers :\n\n```scala\ndef fea: Future[Either[Throwable, Int]] = Future(Right(1))\ndef feb(a: Int): Future[Either[Throwable, Int]] = Future(Right(a + 2))\n\nval composedAB: Future[Int] = for {\n  a \u003c- fea.squash\n  ab \u003c- feb(a).squash\n} yield ab\n\ncomposedAB // Future(\"ab\")\n\nval error: Either[Throwable, Int] = Left(BoomError)\nval composedABWithError: Future[Int] = for {\n  a \u003c- Future.successful(error).squash\n  ab \u003c- feb(a).squash\n} yield ab\n\ncomposedABWithError //Future(Failure(BoomError))\n\n```\n\nComposing several `Future[Try[_]]`s without monad transformers is also possible:\n\n```scala\ndef fta: Future[Try[Int]] = Future(Success(1))\ndef ftb(a: Int): Future[Try[Int]] = Future(Success(a + 2))\n\nval composedAB: Future[Int] = for {\n  a \u003c- fta.squash\n  ab \u003c- ftb(a).squash\n} yield ab\n\ncomposedAB // Future(\"ab\")\n```\n\n### Options\n\nSame operations can be used with options : `FutureSquash.fromOption` and `squash` on `Future[Option[A]]`.\nFor empty options, an `EmptyValueError` will be raised.\n\n## Either additional operations (EitherOps)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.common/eitherops_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.common/eitherops_2.13)\n\nThis small util methods allow to use Either for multiple values validation, to avoid for comprehension fail-fast behavior and accumulate errors.\n\nExample :\n\n```scala\nimport com.softwaremill.eitherops._\n\ncase class Person(firstName: String, lastName: String, age: Int)\n\nsealed trait Error\ncase class NumericError(message: String) extends Error\ncase class OtherError(message: String) extends Error\n\ndef validateAge(intValue: Int): Either[NumericError, Int] = ??? \ndef validateFirstName(stringValue: String): Either[OtherError, String] = ???\ndef validateLastName(stringValue: String): Either[OtherError, String] = ???\n\nval errors: Seq[Error] = EitherOps.collectLefts(\n  validateFirstName(\"john\"),\n  validateLastName(\"doe\"),\n  validateAge(40)\n)\n\nif (errors.isEmpty) ???// use for comprehension here to build a Person \nelse ??? // handle errors here\n```\n\n`EitherOpscollectRights` symmetric method is provided for convenience.\n## Simple benchmarking utilities\n\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.common/futuretry_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.common/benchmarks_2.13)\n\nProvides utilities for benchmarking.\n\n - `Timed.runTests(tests: List[(String, () =\u003e String)], repetitions: Int)`: runs specified number of repetitions of\n  given code blocks and collects results (*mean* and *standard deviation*). A warmup round of all tests will be executed\n  before measuring any statistics. Tests will be executed in random order.\n \n - `Timed.runTests(tests, repetitions, warmup)` runs multiple repetitions of shuffled tests provided as a list of `PerfTest` \n instances. Each `PerfTest` should define a name, body (synchronous code block) and, optionally, an additional code block \n that specifies a \"warmup\" that will be run before each test execution. The `Timed` object can also consume an optional \n `warmup` argument which specified code block that should be executed before all tests. If omitted, the default global warmup \n will run all the provided tests once (without collecting metrics).\n   \nExample:\n\n````scala\n\nval simpleTests: List[(String, () =\u003e String)] = List(\n  (\"test1\", () =\u003e {\n    // do some calculation\n    \"Ok\"\n  }),\n  (\"test2\", () =\u003e {\n    // do some other calculation\n    \"Ok\"\n  })\n)\n\nTimed.runTests(simpleTests, repetitions = 50)\n````\nor\n````scala\n\ncase class MyTest(name: String, param: Int) extends PerfTest {\n\n  override def warmup(): Unit = {\n    // prepare some resources\n  }\n\n  override def run(): Try[String] = {\n    // do some calculations\n    Success(String)\n  }\n}\n  \nval tests: List[MyTest] = List(\n  MyTest(\"test1\", param = 665), MyTest(\"test2\", param = 777)    \n)\n  \nTimed.runTests(tests, repetitions = 50, warmup = (tests: List[MyTest]) =\u003e {\n  // global warmup body\n})\n````\n","funding_links":[],"categories":["工具库"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoftwaremill%2Fscala-common","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsoftwaremill%2Fscala-common","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoftwaremill%2Fscala-common/lists"}