{"id":17970236,"url":"https://github.com/travisbrown/abstracted","last_synced_at":"2025-08-21T10:19:24.752Z","repository":{"id":33922961,"uuid":"37643727","full_name":"travisbrown/abstracted","owner":"travisbrown","description":"Forget your methods","archived":false,"fork":false,"pushed_at":"2017-12-01T00:14:52.000Z","size":18,"stargazers_count":41,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-05T17:02:57.886Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"moby/moby","license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/travisbrown.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}},"created_at":"2015-06-18T07:12:01.000Z","updated_at":"2021-08-06T19:47:09.000Z","dependencies_parsed_at":"2022-08-24T11:20:47.885Z","dependency_job_id":null,"html_url":"https://github.com/travisbrown/abstracted","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/travisbrown/abstracted","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travisbrown%2Fabstracted","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travisbrown%2Fabstracted/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travisbrown%2Fabstracted/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travisbrown%2Fabstracted/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/travisbrown","download_url":"https://codeload.github.com/travisbrown/abstracted/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travisbrown%2Fabstracted/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271462097,"owners_count":24763860,"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","status":"online","status_checked_at":"2025-08-21T02:00:08.990Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2024-10-29T15:02:13.568Z","updated_at":"2025-08-21T10:19:24.718Z","avatar_url":"https://github.com/travisbrown.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Abstracted\n\n[![Build status](https://img.shields.io/travis/travisbrown/abstracted/master.svg)](http://travis-ci.org/travisbrown/abstracted)\n[![Coverage status](https://img.shields.io/codecov/c/github/travisbrown/iteratee/master.svg)](https://codecov.io/github/travisbrown/abstracted)\n[![Maven Central](https://img.shields.io/maven-central/v/io.travisbrown/abstracted_2.11.svg)](https://maven-badges.herokuapp.com/maven-central/io.travisbrown/abstracted_2.11)\n\nThis is a small proof of concept that demonstrates how to implement a Scala\nmacro that allows us to \"forget\" all of a value's methods and only use\nenrichment methods (which will usually be provided via a type class).\n\nThe idea behind `abstracted` was originally (as far as I know) suggested by\n[Michael Pilquist](https://twitter.com/mpilquist) in the\n[cats](https://github.com/non/cats) room on\n[Gitter](https://gitter.im/non/cats?at=5565ecf27a71f1612c266c8d), although the\napproach he suggests is different from the one I've used here.\n\n## Simple example\n\nAs an example, suppose we've got a `Box` type:\n\n```scala\ncase class Box[A](val a: A) {\n  def map[B](f: A =\u003e B): Box[B] = {\n    println(\"Box's map\")\n    Box(f(a))\n  }\n\n  def flatMap[B](f: A =\u003e Box[B]): Box[B] = {\n    println(\"Box's flatMap\")\n    f(a)\n  }\n}\n```\n\nAnd a monad instance for it:\n\n```scala\nimport cats.Monad\n\nimplicit val boxMonad: Monad[Box] = new Monad[Box] {\n  override def map[A, B](fa: Box[A])(f: A =\u003e B): Box[B] = {\n    println(\"Box's functor's map\")\n    Box(f(fa.a))\n  }\n\n  def flatMap[A, B](fa: Box[A])(f: A =\u003e Box[B]): Box[B] = {\n    println(\"Box's monad's flatMap\")\n    f(fa.a)\n  }\n\n  def pure[A](a: A): Box[A] = Box(a)\n}\n```\n\nNow if we use `Box` in a `for`-comprehension, for example, the monad instance\nwon't get used:\n\n```scala\nscala\u003e import io.travisbrown.abstracted.demo._\nimport io.travisbrown.abstracted.demo._\n\nscala\u003e import cats.syntax.all._\nimport cats.syntax.all._\n\nscala\u003e for { foo \u003c- Box(\"foo\"); howMany \u003c- Box(3) } yield foo * howMany\nBox's flatMap\nBox's map\nres0: io.travisbrown.abstracted.demo.Box[String] = Box(foofoofoo)\n```\n\nOur `abstracted` macro allows us to change this:\n\n```scala\nscala\u003e import io.travisbrown.abstracted._\nimport io.travisbrown.abstracted._\n\nscala\u003e for { foo \u003c- Box(\"foo\").abstracted; howMany \u003c- Box(3) } yield foo * howMany\nBox's monad's flatMap\nBox's map\nres1: io.travisbrown.abstracted.demo.Box[String] = Box(foofoofoo)\n```\n\n## Finagle services\n\nI decided to take a stab at implementing `abstracted` tonight because of\n[a conversation about how Finagle services compose](https://github.com/twitter/finagle/issues/385)\nthis afternoon. Finagle services are morally more or less Kleisli arrows over\nTwitter futures, but for whatever reason `Service` extends `I =\u003e Future[O]`,\nwhich means that they have totally useless `compose` and `andThen` methods. In\nanother [project](https://github.com/travisbrown/catbird) I provide category\nand profunctor instances for `Service`, but the `compose` and `andThen`\nenrichment methods provided by cats for things with `Compose` instances are\nblocked by the stupid methods that `Service` inherits from `Function1`.\n\nFor example, if we've got these services:\n\n```scala\nimport cats.syntax.compose._\nimport com.twitter.util.Future\nimport com.twitter.finagle.Service\nimport io.catbird.finagle._\n\nval is = Service.mk[Int, String](i =\u003e Future.value(i.toString))\nval si = Service.mk[String, Int](s =\u003e Future(s.toInt))\n```\n\nWe get an error when we try to compose them:\n\n```scala\nscala\u003e val ss = si andThen is\n\u003cconsole\u003e:22: error: type mismatch;\n found   : com.twitter.finagle.Service[Int,String]\n required: com.twitter.util.Future[Int] =\u003e ?\n              si andThen is\n                         ^\n```\n\nOur `abstracted` macro fixes this problem:\n\n```scala\nscala\u003e import io.travisbrown.abstracted._\nimport io.travisbrown.abstracted._\n\nscala\u003e val ss = si.abstracted andThen is\nss: com.twitter.finagle.Service[String,String] = \u003cfunction1\u003e\n```\n\n## How it works\n\nThe implementation is pretty straightforward. First we've got an implicit class\nthat provides a `def abstracted: Empty[A]` method for any `A`, where our `Empty`\ntype is a case class that wraps an `A` and provides access to the wrapped value,\nbut doesn't have any other methods.\n\nWe also have a `Converter[A, B]` type that represents a conversion from\n`Empty[A]` to `B` (I ran into problems trying to use `Empty[A] =\u003e B` directly),\nand an implicit method that will apply the conversion automatically to any\n`Empty[A]` for any appropriately-typed `Converter` instance.\n\nThe interesting part is how we make `Converter` instances. We use the Scala\nmacro system's [fundep materialization](http://docs.scala-lang.org/overviews/macros/implicits.html), which allows us to determine in the body of the macro what the output\ntype of the `Converter` will be. We look at the open implicits and find one that\nlooks like the compiler is fishing for a `WhateverOps` enrichment class for our\n`Empty[A]`. We then ask for a view from `A` (our real type) to the target of\nthat view. We read the return type off the view from `A`, and from there the\nimplementation is pretty trivial.\n\n## Status\n\nIt seems like it works. The examples above can be run by opening up a REPL with\n`sbt demo/console`. If other people think it looks useful I guess it could end\nup in cats, although there's nothing cats-specific about the macro itself or the\nsurrounding machinery.\n\n## License\n\nabstracted is licensed under the **[Apache License, Version 2.0][apache]** (the\n\"License\"); you may not use this software except in compliance with the License.\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n[apache]: http://www.apache.org/licenses/LICENSE-2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftravisbrown%2Fabstracted","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftravisbrown%2Fabstracted","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftravisbrown%2Fabstracted/lists"}