├── .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 | [](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 |
--------------------------------------------------------------------------------