{"id":23494495,"url":"https://github.com/tkroman/puree","last_synced_at":"2025-04-18T05:49:38.477Z","repository":{"id":57729084,"uuid":"188735310","full_name":"tkroman/puree","owner":"tkroman","description":"purity enforcer","archived":false,"fork":false,"pushed_at":"2019-07-22T19:53:08.000Z","size":96,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-16T13:54:26.455Z","etag":null,"topics":["compiler-plugin","effects","fp","purity","scala"],"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/tkroman.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-05-26T21:54:40.000Z","updated_at":"2019-11-18T21:45:30.000Z","dependencies_parsed_at":"2022-09-10T23:41:31.581Z","dependency_job_id":null,"html_url":"https://github.com/tkroman/puree","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tkroman%2Fpuree","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tkroman%2Fpuree/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tkroman%2Fpuree/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tkroman%2Fpuree/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tkroman","download_url":"https://codeload.github.com/tkroman/puree/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249190725,"owners_count":21227424,"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":["compiler-plugin","effects","fp","purity","scala"],"created_at":"2024-12-25T03:11:03.250Z","updated_at":"2025-04-16T03:28:41.305Z","avatar_url":"https://github.com/tkroman.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# puree\nA Scala compiler plugin to warn about unused effects\n\n[![CircleCI](https://circleci.com/gh/tkroman/puree.svg?style=svg)](https://circleci.com/gh/tkroman/puree)\n\n# sbt setup\n\n```scala\nlazy val pureeV = \"0.0.10\"\nlibraryDependencies ++= Seq(\n  compilerPlugin(\"com.tkroman\" %% \"puree\" % pureeV),\n  \"com.tkroman\" %% \"puree-api\" % pureeV % Provided\n)\n\n// very desirable\nscalacOptions ++= Seq(\n  \"-Ywarn-unused:implicits\",\n  \"-Ywarn-unused:imports\",\n  \"-Ywarn-unused:locals\",\n  \"-Ywarn-unused:params\",\n  \"-Ywarn-unused:patvars\",\n  \"-Ywarn-unused:privates\",\n  \"-Werror\",\n  \"-Ywarn-value-discard\",\n)\n```\n\n# Disabling Puree selectively\n\nWe also ship the `puree-api` package which provides an `@intended` annotation\nthat users can use whenever they want to disable checking for a chunk of code.\n\nNote: `@intended` also takes optional explanation argument.\n\n```scala\nimport com.tkroman.puree.annotation.intended\n\n@intended\nclass GoingDirty {\n  def f(): Future[Int] = Future(1)\n  def g(): Int = {\n    f() // I mean... you asked for it\n    1\n  }\n}\n```\nThis will compile fine.\n\nAnother realistic usecase is builders:\n\n```scala\nimport com.tkroman.puree.annotation.intended\n\nval buf = List.newBuilder[Byte]\n(buf += 0xf.toByte): @intended(\"not calling .result() here\")\nbuf.result()\n```\n\nIn future some of these use-cases may be heuristically solved by the library\nbut at this point we prefer to remain as flexible as possible.\n\n# Strictness configuration\n\nPuree supports (currently) 3 strictness levels:\n- `off`: Every check is disabled. Same as removing the plugin completely.\n- `effects`: Only `F[_*]` checks are performed\n- `strict`: Any non-unit expression that is not in the return position\n    (i.e. is not the last statement of the enclosing expression) is considered \"unused\" value.\n    This can be pretty hard on most code so should be enabled at one's own discretion.\n\nDefault level is `effects`. To customize, use one of the following flags:\n\n```scala\nscalacOptions += Seq(\"-P:puree:level:off\")\nscalacOptions += Seq(\"-P:puree:level:effects\")\nscalacOptions += Seq(\"-P:puree:level:strict\")\n```\n\n# Fine-grained control\n\nIt is possible to override behavior for individual methods, which is useful\nwhen users want to override some behavior on a system-wide level without\nindividual suppression via `@intended`.\n\nIf there is a `puree-settings` file on a compilation classpath,\nfine-grained settings will be read from it. File format:\n\n```\n[off]\nfoo.bar.Baz.::=\n\n[effect]\nfoo.bar.Quux.methodName\n\n[strict]\nfoo.bar.Quack.::\n```\n\nEach section is optional.\n\nMotivation: e.g. `scala.collection.mutable.Builder.+=` method returns\n`this.type` for each builder instance, and since `Builder` is an `F[_, _]`,\ncode like\n\n```scala\nval buf = List.newBuilder[Int]\nbuf += 1\nbuf.result()\n```\n\nwill be flagged as suspicious. To avoid this, just configure puree with this:\n\n```\n[off]\nscala.collection.mutable.Builder.+=\n```\n\nSubtyping checks are performed as expected, so e.g. since `Builder` is a subtype\nof `Growable`, a warning will not be raised on `Builder` instances invoking `+=`\nif `Growable.+=`'s level is set to `Off` in settings.\n\nIt's possible to always warn on select methods even if a global level is `Off`:\n\n```\n[strict]\nscala.collection.mutable.Builder.+=\n```\n\nmeans that `+=` invocations will _aways_ trigger warnings.\n\n# Why\n\n## Effects\n\nIn essence, we say that effect is everything that is not a simple value, so\n\n```scala\nval intIsNotAnEffect = 1\nval dateIsNotAnEffect = LocalDate.now()\nval stringIsNotAnEffect = \"no, I'm not\"\n\nval optionIsAnEffect = Some(5)\nval listIsAnEffect = List(1, 2, 3)\nval taskIsAnEffect = Task(println(\"yes, I am\"))\nval ioIsAnEffect = IO(\"me too\")\nval programsAreEffectsOfSorts: IO[Unit] = completeAppInIO\n// I don't want to mention Future, but...\n```\n\nIn a pure FP setting, most effects are pure,\ni.e. declaring or referring to and effect does not mean\nany sort of computation being started.\n\nHence the idea that if somewhere in your code there is this:\n\n```scala\ndef read: Task[String] = Task(in.read())\ndef write(str: String) = Task(out.write(str))\n\nval prompt: Task[Int = {\n    write(\"Enter a number\")\n    val number = read()\n    number.flatMap(n =\u003e Task.fromTry(Try(n.toInt)))\n    // use that int\n}\n```\n\nyou will be surprised by an absence of the prompt string,\nwhich will happen because the `write(...)` expression\ndoesn't actually launch the printing routine.\n\nMore than that, in most of the cases a presence\nof an unused effectful value alone means it's likely a bug, a typo\nor an omission of sorts. Think of a trivial example:\n\n```scala\nsomeCode()\nList(1,2,3) // What? Why?\nsomethingElse()\n```\n\nNormally, `scalac` will warn you if you use a pure expression in a useless context, e.g\n\n```scala\nval x = 5\n1 // warning here\nval y = 10\n```\n\nBut it fails to see more complicated examples:\n\n```scala\ndef f = 1\nval x = 1\nf // no warning\nval y = 2\n```\n\nScala can't help you out here because believing that all functions are pure\nis not practical in general.\nHowever, if you tru writing your code in a more or less principled way,\nmost of the time this will be true for almost all effectful values and functions.\nThink of it as  \"If I return an `F[_, _*]`\", I probably wanted to use it.\n\nThis plugin is provided specifically as a way to help you with that.\n\n# Notes\nWorks best if you also enable the following scalac flags:\n\n```\n-Ywarn-unused:implicits\n-Ywarn-unused:imports\n-Ywarn-unused:locals\n-Ywarn-unused:params\n-Ywarn-unused:patvars\n-Ywarn-unused:privates\n-Xfatal-warnings\n-Ywarn-value-discard\n```\n\nThis plugin does not make an attempt to be too smart, the rules are pretty simple:\nif there is an `F[_, _*]`, it's not assigned to anythings,\nis not composed with anythings, and is not a last expression in the block,\nit's considered to be worthy a warning. Making warnings into errors via `Xfatal-warnings`\ntakes care of the rest.\n\nWe also don't try taking over other warings, so there are no additional rules.\n\n\nA more comprehensive set of scalac flags one should almost always enable\nif they want to maximize compiler's help can be found here:\nhttps://tpolecat.github.io/2017/04/25/scalac-flags.html\n\n# License\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftkroman%2Fpuree","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftkroman%2Fpuree","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftkroman%2Fpuree/lists"}