├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.sbt ├── project ├── build.properties └── plugins.sbt ├── src ├── main │ └── scala │ │ ├── typeclass.scala │ │ └── typeclassic.scala └── test │ └── scala │ └── test.scala └── version.sbt /.gitignore: -------------------------------------------------------------------------------- 1 | project/boot 2 | target 3 | .ensime 4 | .ensime_lucene 5 | TAGS 6 | \#*# 7 | *~ 8 | .#* 9 | .lib 10 | .history 11 | .*.swp 12 | .idea 13 | .idea/* 14 | .idea_modules 15 | .DS_Store 16 | .sbtrc 17 | *.sublime-project 18 | *.sublime-workspace 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: scala 3 | scala: 4 | - 2.11.7 5 | - 2.10.6 6 | jdk: oraclejdk8 7 | 8 | cache: 9 | directories: 10 | - $HOME/.ivy2/cache 11 | - $HOME/.sbt 12 | 13 | before_cache: 14 | - find "$HOME/.sbt" -name "*.lock" -print -delete 15 | - find "$HOME/.ivy2" -name "ivydata-*.properties" -print -delete 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # typeclassic 2 | 3 | [![Join the chat at https://gitter.im/typelevel/typeclassic](https://badges.gitter.im/typelevel/typeclassic.svg)](https://gitter.im/typelevel/typeclassic?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | Everything you need to make type classes first class. 6 | 7 | ## What is this? 8 | 9 | This repository is intended to integrate (or merge) [simulacrum](https://github.com/mpilquist/simulacrum), [machinist](https://github.com/typelevel/machinist), [imp](https://github.com/non/imp), [export-hook](https://github.com/milessabin/export-hook), and possibly other projects. 10 | 11 | The goal is to produce a library which is mostly compile-time only (but may have a very small runtime component) which enables us to define and use type classes in a concise, efficient, and idiomatic way. 12 | 13 | ## Maintainers 14 | 15 | The current maintainers (pulled from the various constituent projects) are: 16 | 17 | * Erik Osheim 18 | * Michael Pilquist 19 | * Miles Sabin 20 | * Tom Switzer 21 | 22 | ## License 23 | 24 | All code is available to you under the Apache 2.0 license, available at https://www.apache.org/licenses/LICENSE-2.0 and also in the LICENSE file. 25 | 26 | Copyright the maintainers, 2015-2016. 27 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import ReleaseTransformations._ 2 | 3 | lazy val typeclassicSettings = Seq( 4 | organization := "org.typelevel", 5 | licenses := Seq(("Apache-2.0", url("https://www.apache.org/licenses/LICENSE-2.0"))), 6 | homepage := Some(url("http://github.com/typelevel/typeclassic")), 7 | 8 | scalaVersion := "2.11.7", 9 | crossScalaVersions := Seq("2.10.6", "2.11.7"), 10 | 11 | scalacOptions ++= Seq( 12 | "-feature", 13 | "-deprecation", 14 | "-unchecked" 15 | ), 16 | libraryDependencies ++= Seq( 17 | "org.scala-lang" % "scala-compiler" % scalaVersion.value % "provided", 18 | "org.scala-lang" % "scala-reflect" % scalaVersion.value, 19 | "org.typelevel" %% "macro-compat" % "1.1.1", 20 | compilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full) 21 | ), 22 | compileOrder := CompileOrder.JavaThenScala, 23 | releaseCrossBuild := true, 24 | releasePublishArtifactsAction := PgpKeys.publishSigned.value, 25 | publishMavenStyle := true, 26 | publishArtifact in Test := false, 27 | pomIncludeRepository := Function.const(false), 28 | 29 | publishTo := Some(if (isSnapshot.value) Opts.resolver.sonatypeSnapshots else Opts.resolver.sonatypeStaging), 30 | 31 | pomExtra := ( 32 | 33 | git@github.com:typelevel/typeclassic.git 34 | scm:git:git@github.com:typelevel/typeclassic.git 35 | 36 | 37 | 38 | mpilquist 39 | Michael Pilquist 40 | http://github.com/mpilquist/ 41 | 42 | 43 | milessabin 44 | Miles Sabin 45 | http://github.com/milessabin/ 46 | 47 | 48 | d_m 49 | Erik Osheim 50 | http://github.com/non/ 51 | 52 | 53 | tixxit 54 | Tom Switzer 55 | http://github.com/tixxit/ 56 | 57 | 58 | ), 59 | 60 | releaseProcess := Seq[ReleaseStep]( 61 | checkSnapshotDependencies, 62 | inquireVersions, 63 | runClean, 64 | runTest, 65 | setReleaseVersion, 66 | commitReleaseVersion, 67 | tagRelease, 68 | publishArtifacts, 69 | setNextVersion, 70 | commitNextVersion, 71 | ReleaseStep(action = Command.process("sonatypeReleaseAll", _)), 72 | pushChanges)) 73 | 74 | lazy val noPublish = Seq( 75 | publish := {}, 76 | publishLocal := {}, 77 | publishArtifact := false) 78 | 79 | lazy val root = project 80 | .in(file(".")) 81 | .aggregate(typeclassicJS, typeclassicJVM) 82 | .settings(name := "typeclassic-root") 83 | .settings(typeclassicSettings) 84 | .settings(noPublish) 85 | 86 | lazy val typeclassic = crossProject 87 | .crossType(CrossType.Pure) 88 | .in(file(".")) 89 | .settings(name := "typeclassic") 90 | .settings(typeclassicSettings: _*) 91 | 92 | lazy val typeclassicJVM = typeclassic.jvm 93 | 94 | lazy val typeclassicJS = typeclassic.js 95 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.11 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") 2 | addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.3") 3 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "0.5.0") 4 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.7") 5 | -------------------------------------------------------------------------------- /src/main/scala/typeclass.scala: -------------------------------------------------------------------------------- 1 | package typeclassic 2 | 3 | import scala.annotation.{ compileTimeOnly, StaticAnnotation } 4 | import scala.language.experimental.macros 5 | import scala.reflect.macros.whitebox.Context 6 | 7 | import macrocompat._ 8 | 9 | @compileTimeOnly("typeclass annotation should have been automatically removed but was not") 10 | class typeclass(excludeParents: List[String] = Nil, generateAllOps: Boolean = true) extends StaticAnnotation { 11 | def macroTransform(annottees: Any*): Any = macro TypeClassMacros.generateTypeClass 12 | } 13 | 14 | 15 | @bundle 16 | class TypeClassMacros(val c: Context) { 17 | import c.universe._ 18 | 19 | def generateTypeClass(annottees: c.Expr[Any]*): c.Expr[Any] = { 20 | annottees.map(_.tree) match { 21 | case (typeClass: ClassDef) :: Nil => modify(typeClass, None) 22 | case (typeClass: ClassDef) :: (companion: ModuleDef) :: Nil => modify(typeClass, Some(companion)) 23 | case other :: Nil => 24 | c.abort(c.enclosingPosition, "@typeclass can only be applied to traits or abstract classes that take 1 type parameter which is either a proper type or a type constructor") 25 | } 26 | } 27 | 28 | private def modify(typeClass: ClassDef, companion: Option[ModuleDef]) = { 29 | val (tparam, proper) = typeClass.tparams match { 30 | case hd :: Nil => 31 | hd.tparams.size match { 32 | case 0 => (hd, true) 33 | case 1 => (hd, false) 34 | case n => c.abort(c.enclosingPosition, "@typeclass may only be applied to types that take a single proper type or type constructor") 35 | } 36 | case other => c.abort(c.enclosingPosition, "@typeclass may only be applied to types that take a single type parameter") 37 | } 38 | 39 | val modifiedTypeClass = typeClass // TODO 40 | 41 | val modifiedCompanion = generateCompanion(typeClass, tparam, proper, companion match { 42 | case Some(c) => c 43 | case None => q"object ${typeClass.name.toTermName} {}" 44 | }) 45 | 46 | val result = c.Expr(q""" 47 | $modifiedTypeClass 48 | $modifiedCompanion 49 | """) 50 | 51 | trace(s"Generated type class ${typeClass.name}:\n" + showCode(result.tree)) 52 | 53 | result 54 | } 55 | 56 | private def generateCompanion(typeClass: ClassDef, tparam0: TypeDef, proper: Boolean, comp: Tree): Tree = { 57 | val tparam = eliminateVariance(tparam0) 58 | 59 | val q"$mods object $name extends ..$bases { ..$body }" = comp 60 | 61 | q""" 62 | $mods object $name extends ..$bases { 63 | import scala.language.experimental.macros 64 | ..$body 65 | ${generateInstanceSummoner(typeClass, tparam)} 66 | } 67 | """ 68 | } 69 | 70 | private def generateInstanceSummoner(typeClass: ClassDef, tparam: TypeDef): Tree = { 71 | q""" 72 | @_root_.typeclassic.op("$$y {$$x}") 73 | def apply[$tparam](implicit x1: ${typeClass.name}[${tparam.name}]): ${typeClass.name}[${tparam.name}] = 74 | macro _root_.typeclassic.OpsMacros.op10 75 | """ 76 | } 77 | 78 | // This method is from simulacrum, contributed by paulp, and is licensed under 3-Clause BSD 79 | private def eliminateVariance(tparam: TypeDef): TypeDef = { 80 | // If there's another way to do this I'm afraid I don't know it. 81 | val u = c.universe.asInstanceOf[c.universe.type with scala.reflect.internal.SymbolTable] 82 | val tparam0 = tparam.asInstanceOf[u.TypeDef] 83 | val badFlags = (Flag.COVARIANT | Flag.CONTRAVARIANT).asInstanceOf[Long] 84 | val fixedMods = tparam0.mods & ~badFlags 85 | TypeDef(fixedMods.asInstanceOf[c.universe.Modifiers], tparam.name, tparam.tparams, tparam.rhs) 86 | } 87 | 88 | private def trace(s: => String) = { 89 | // Macro paradise seems to always output info statements, even without -verbose 90 | if (sys.props.get("typeclassic.trace").isDefined) c.info(c.enclosingPosition, s, false) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/scala/typeclassic.scala: -------------------------------------------------------------------------------- 1 | package typeclassic 2 | 3 | import scala.annotation.StaticAnnotation 4 | import scala.language.experimental.macros 5 | import scala.reflect.macros.whitebox 6 | import macrocompat.bundle 7 | 8 | /** 9 | * Typeclassic's ops macros are a generalization of machinist. 10 | * 11 | * The goal is to be able to rewrite macro applications into some 12 | * method call on a combination of arguments. We need to be able to 13 | * "change" which object's method is invoked, the name of the method, 14 | * and the order of the arguments. 15 | * 16 | * @op syntax: 17 | * 18 | * Paramters consist of a $ followed by one-or-more lowercase letters 19 | * (a-z). These parameter names will correspond to the parameter trees 20 | * matched by the parseApplication() method. An @op prototype MUST 21 | * mention every tree -- if there are 3 trees, there must be 3 22 | * parameter names. Unused parameter names can be specified via a 23 | * trailing {} stanza. 24 | * 25 | * Examples: 26 | * 27 | * $y.compare($x, $z) 28 | * In this example the type class is $y, the second parameter tree. 29 | * It's like that something like ops(a)(ev).compare(b) was matched. 30 | * 31 | * $y {$x} 32 | * In this example we are throwing away the first parameter and just 33 | * returning the second. It's likely we matched Companion.method(ev) 34 | * and just want to get the ev value directly. 35 | * 36 | * $y.negate($x) 37 | * In this example we likely matched ops(a)(ev).unary_-(). 38 | * 39 | * You could imagine all kinds of detailed rewrites with many function 40 | * applications. Currently a prototype supports at-most one function 41 | * application (the starting parameter followed by a dot and a name) 42 | * but this is just a limitation of the current parser. 43 | * 44 | * One nice thing is that we should be able to use this to "inline" 45 | * certain simple implementations, for example you could imagine 46 | * something like: 47 | * 48 | * implicit class LitOps(n: Int) { 49 | * @op("Real($x) * $y") 50 | * def *(x1: Real): Real = Ops.macros10 51 | * } 52 | * 53 | * This handles a huge case of implicit operators which machinist is 54 | * not currently able to deal with (machinist requires a type class 55 | * instance to rewrite to, rather than this more generate prototype 56 | * strategy). 57 | */ 58 | @bundle 59 | class OpsMacros(val c: whitebox.Context) { 60 | import c.universe._ 61 | 62 | // abort compilation with a hopefully helpful message 63 | def die(s: String): Nothing = 64 | c.abort(c.enclosingPosition, s) 65 | 66 | // determine if a given annotation is @op or not. 67 | def isOp(a: Annotation): Boolean = 68 | a.tree.tpe =:= typeOf[op] 69 | 70 | // look for an @op containing the prototype we need for our macros 71 | def getProto(name: TermName): Option[String] = 72 | c.prefix.tree.tpe.decl(name).asMethod.annotations.filter(isOp) match { 73 | case List(ann) => 74 | ann.tree.children.tail match { 75 | case List(Literal(Constant(proto))) => 76 | Some(proto.asInstanceOf[String]) 77 | case _ => None 78 | } 79 | case _ => None 80 | } 81 | 82 | // we will definitely need to add more shapes here as more cases go 83 | // up. the key is that we need to add all values that might possibly 84 | // be arguments (or have methods invoked on them) in the order that 85 | // they appeared. 86 | def parseApplication(t: Tree): (List[Tree], TermName) = { 87 | t match { 88 | case Select(Apply(Apply(TypeApply(_, _), cs), ds), TermName(name)) => 89 | (cs ::: ds, TermName(name)) 90 | case Apply(Select(Apply(Apply(TypeApply(_, _), cs), ds), TermName(name)), es) => 91 | (cs ::: ds ::: es, TermName(name)) 92 | case Apply(TypeApply(Select(c, TermName(name)), _), ds) => 93 | (c :: ds, TermName(name)) 94 | case t => 95 | die(s"cannot parse application shape: ${showRaw(t)}") 96 | } 97 | } 98 | 99 | // parse the macro application, and look for a prototype 100 | def parse(t: Tree): (List[Tree], String) = { 101 | val (trees, term) = parseApplication(t) 102 | getProto(term) match { 103 | case Some(proto) => (trees, proto) 104 | case _ => die(s"could not find an @op annotation: $t") 105 | } 106 | } 107 | 108 | // useful regular expressions 109 | val V = """(\$[a-z]+)""".r 110 | val P1 = """^(\$[a-z]+)\.([^()]+)(.+)$""".r 111 | val P2 = """^\(([^()]+)\)$""".r 112 | val I = """^(.+) \{[^}]*\}$""".r 113 | 114 | // this lexer is currently way too brittle but works for the given 115 | // cases. it definitely should be made more rigorous. 116 | def lexer(s: String, table: Map[String, Tree]): Tree = 117 | s match { 118 | case I(str) => 119 | lexer(str, table) 120 | case V(name) => 121 | table(name) 122 | case P1(obj, meth, rest) => 123 | val toks = P2.findAllMatchIn(rest).map(_.group(1)).toList 124 | val stanzas = toks.map(_.split(", ").toList) 125 | (obj, meth, stanzas) 126 | val t0: Tree = Select(table(obj), TermName(meth)) 127 | stanzas.foldLeft(t0)((t, stanza) => Apply(t, stanza.map(table(_)))) 128 | } 129 | 130 | // this is the top-level macro method that is called. it 131 | // auto-detects everything it needs to create a new tree (the 132 | // argument trees to the macro application and the prototype), and 133 | // then constructs a new tree. 134 | // 135 | // we let scalac re-typecheck the tree, so at this moment we don't 136 | // have to worry if the types line up or not. 137 | def interpret(): Tree = { 138 | val t = c.macroApplication 139 | val (trees, proto) = parse(t) 140 | val names = V.findAllMatchIn(proto).map(_.matched).toList.sorted 141 | val table = (names zip trees).toMap 142 | val tree = lexer(proto, table) 143 | println(t) 144 | println(s" becomes $tree") 145 | println("") 146 | tree 147 | } 148 | 149 | // an ugly thing is that we have to support all possible types 150 | // here. that's why matching trees rather than types is nice -- 151 | // there are fewer shapes we have to support. 152 | // 153 | // we don't have to match the "left-hand side" arguments here, only 154 | // the "right-hand side" ones. 155 | // 156 | // for example, suppose we wanted to rewrite something like: 157 | // 158 | // new WeirdOps(x, y, z)(ev0, ev1).method(u, v)(ev2) 159 | // 160 | // we would use the op21 method, since .method has two argument 161 | // lists, the first with 2 arguments, the second with 1. 162 | // 163 | // it's also worth nothing that the names *must* line up in 164 | // macros. this means that you'll be using names like x1, x2, y1, 165 | // and so on. 166 | 167 | def op00(): Tree = interpret() 168 | def op01()(y1: Tree): Tree = interpret() 169 | def op02()(y1: Tree, y2: Tree): Tree = interpret() 170 | def op03()(y1: Tree, y2: Tree, y3: Tree): Tree = interpret() 171 | 172 | def op10(x1: Tree): Tree = interpret() 173 | def op11(x1: Tree)(y1: Tree): Tree = interpret() 174 | def op12(x1: Tree)(y1: Tree, y2: Tree): Tree = interpret() 175 | def op13(x1: Tree)(y1: Tree, y2: Tree, y3: Tree): Tree = interpret() 176 | 177 | def op20(x1: Tree, x2: Tree): Tree = interpret() 178 | def op21(x1: Tree, x2: Tree)(y1: Tree): Tree = interpret() 179 | def op22(x1: Tree, x2: Tree)(y1: Tree, y2: Tree): Tree = interpret() 180 | def op23(x1: Tree, x2: Tree)(y1: Tree, y2: Tree, y3: Tree): Tree = interpret() 181 | 182 | def op30(x1: Tree, x2: Tree, x3: Tree): Tree = interpret() 183 | def op31(x1: Tree, x2: Tree, x3: Tree)(y1: Tree): Tree = interpret() 184 | def op32(x1: Tree, x2: Tree, x3: Tree)(y1: Tree, y2: Tree): Tree = interpret() 185 | def op33(x1: Tree, x2: Tree, x3: Tree)(y1: Tree, y2: Tree, y3: Tree): Tree = interpret() 186 | } 187 | 188 | /** 189 | * The op annotation is used to pass along rewriting information that 190 | * the macro implementation needs to produce new trees. 191 | * 192 | * The long-term vision is that a simulacrum-like macro would be 193 | * generating the implicit Ops class, including its methods and @op 194 | * annotations. A different (but related) annotation would be needed 195 | * to guide simulacrum's decisions about creating this Ops class. 196 | */ 197 | final class op(final val proto: String) extends StaticAnnotation 198 | 199 | // some examples to prove this stuff kind of works 200 | object Example { 201 | 202 | trait Semigroup[A] { 203 | def combine(x: A, y: A): A 204 | } 205 | 206 | object Semigroup { 207 | 208 | // our own optimized apply -- equivalent to imp's summon 209 | @op("$y {$x}") 210 | def apply[A](implicit x1: Semigroup[A]): Semigroup[A] = 211 | macro OpsMacros.op10 212 | 213 | implicit val intSemigroup: Semigroup[Int] = 214 | new Semigroup[Int] { 215 | def combine(x: Int, y: Int): Int = x + y 216 | } 217 | 218 | implicit class Ops[A](a: A)(implicit ev: Semigroup[A]) { 219 | // testing the semigroup's combine operator 220 | @op("$y.combine($x, $z)") 221 | def |+|(x1: A): A = macro OpsMacros.op10 222 | } 223 | } 224 | 225 | import scala.math.Ordering 226 | 227 | implicit class TestOps[A](a: A)(implicit ev: Ordering[A]) { 228 | // testing a comparison operator on the existing ordering type class 229 | @op("$y.compare($x, $z)") 230 | def <=>(x1: A): Int = macro OpsMacros.op10 231 | } 232 | 233 | // our own optimized "implicitly" -- equivalent to imp's imp 234 | @op("$y {$x}") 235 | def implicitly_[T](implicit x1: T): T = 236 | macro OpsMacros.op10 237 | 238 | } 239 | -------------------------------------------------------------------------------- /src/test/scala/test.scala: -------------------------------------------------------------------------------- 1 | package typeclassic 2 | 3 | object Test { 4 | import Example.TestOps 5 | import Example.Semigroup.Ops 6 | val res1 = 333 <=> 444 7 | val res2 = 1 |+| 2 8 | val res3 = Example.Semigroup[Int] 9 | val res4 = Example.implicitly_[Ordering[Int]] 10 | 11 | @typeclass trait Semigroup[A] { 12 | def combine(x: A, y: A): A 13 | } 14 | object Semigroup { 15 | implicit val sgInt: Semigroup[Int] = new Semigroup[Int] { 16 | def combine(x: Int, y: Int) = x + y 17 | } 18 | } 19 | Semigroup[Int].combine(1, 2) 20 | } 21 | -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | version in ThisBuild := "0.0.1-SNAPSHOT" 2 | --------------------------------------------------------------------------------