package feature
- Alphabetic
- Public
- All
Type Members
-
final
class
Caller
[+A] extends AnyVal
An implicit value that points to the function caller.
An implicit value that points to the function caller.
Usage
libraryDependencies += "com.thoughtworks.feature" %% "caller" % "latest.release"
Getting the caller for logging or something
object Foo{ def log()(implicit caller: Caller[Any]) = { println(caller.value) } } object Bar{ Foo.log() // Bar }
Restricting who you can be called from
class IKnowWhatImDoing object Foo{ def runDangerous()(implicit caller: Caller[IKnowWhatImDoing]) = { println(caller.value) } } object Bar { Foo.runDangerous() // compile error } object Bar2 extends IKnowWhatImDoing{ Foo.runDangerous() // ok, prints Bar2 }
Getting calling class or classloader, e.g. for loading resources, without needing to worry about properly setting up and tearing down the Context ClassLoader
object Foo{ def getResource(path: String)(implicit caller: Caller[_]) = { caller.value.getClass.getClassLoader.getResourceAsStream(path) } } object Bar{ Foo.getResource("/thing/file.txt") // loads resource from `Bar`s classloader, always }
-
final
class
Constructor
[F] extends AnyVal
An implicit value for dynamically creating classes and traits, especially dynamic mixins.
An implicit value for dynamically creating classes and traits, especially dynamic mixins.
Usage
libraryDependencies += "com.thoughtworks.feature" %% "constructor" % "latest.release"
trait A trait B def makeAWithB()(implicit constructor: Constructor[() => A with B]): A with B = { constructor.newInstance() } val ab: A with B = makeAWithB()
Motivation
This feature is useful for library authors. A library author may ask his user to create a
trait
type, then dynamically mix-in it with the features provided by the library.Suppose you are creating a DSL that compiles to JavaScript.
You want your DSL is extensible. For example, the DSL users should be able to create custom binary operators.
With the help of
Constructor.scala
, you can put the boilerplate code into a private classBinaryOperator
:trait Ast object Ast { class Literal(val n: Int) extends Ast { override final def compile(): String = n.compile() } private[Ast] abstract class BinaryOperator(leftHandSide: Ast, rightHandSide: Ast) extends Ast { protected def symbol: String override final def compile() = s"($leftHandSide $symbol $rightHandSide)" } def binaryOperator[T](leftHandSide: Ast, rightHandSide: Ast)( implicit constructor: Constructor[(Ast, Ast) => BinaryOperator with T]): BinaryOperator with T = { constructor.newInstance(leftHandSide, rightHandSide) } }
The users of only need a very simple implementation for their custom binary operators.
import Ast._ trait Plus { protected final def symbol = "+" } trait Minus { protected final def symbol = "-" } val myAst = binaryOperator[Plus]( new Literal(1), binaryOperator[Minus]( new Literal(3), new Literal(5) ) ) print(myAst.compile()) // Output: "(1 + (3 - 5))"
An alternative approach
There is another approach to integrate partial implementation from users: asking users to provide custom callback functions or type classes.
However, the callback functions or type classes approach will create additional object instances and additional references for each instance at run-time. On the other hand, the
Constructor.scala
approach create classes at compile-time and no additional run-time references. As a result, at run-time,Constructor.scala
approach will consume less memory, and performs less indirect access on memory.Author:
杨博 (Yang Bo) <[email protected]>
-
trait
Demixin
[ConjunctionType] extends AnyRef
A type class that converts a mix-in type to shapeless.HList.
A type class that converts a mix-in type to shapeless.HList.
Common imports
You may want to use Demixin with shapeless.HList.
import shapeless._ import org.scalatest.Matchers._
The Demixin type class can be summoned from Demixin.apply method:
class A; trait B; object C; val demixin = Demixin[A with B with C.type with String with Int]
Out should be a shapeless.HList of each type components in the mix-in type
ConjunctionType
."implicitly[demixin.Out =:= (A :: B :: C.type :: String :: Int :: HNil)]" should compile
The elements in Out should keep the same order as type components in
ConjunctionType
."implicitly[demixin.Out =:!= (String :: A :: B :: C.type :: Int :: HNil)]" should compile
, Out of Demixin on scala.Any should be shapeless.HNil
val demixin = Demixin[Any] "implicitly[demixin.Out =:= HNil]" should compile
, Out of Demixin on non-mixed-in types should be a shapeless.HList that contains only one element
val demixin = Demixin[String] "implicitly[demixin.Out =:= (String :: HNil)]" should compile
Examples: -
trait
Factory
[Output] extends AnyRef
A factory to create new instances, especially dynamic mix-ins.
A factory to create new instances, especially dynamic mix-ins.
Author:
杨博 (Yang Bo) <[email protected]>
Given a trait that has an abstract member.
trait Foo { var bar: Int }
When creating a factory for the trait.
val factory = Factory[Foo]
Then the newInstance method of the factory should accept one parameter.
val foo: Foo = factory.newInstance(bar = 1) foo.bar should be(1)
, Given two traits that have no abstract member.
trait Foo trait Bar
When creating a factory for mix-in type of the two types.
val factory = Factory[Foo with Bar]
Then the newInstance method of the factory should accept no parameters.
val fooBar: Foo with Bar = factory.newInstance() fooBar should be(a[Foo]) fooBar should be(a[Bar])
, Given a trait that contains an abstract method annotated as @inject.
import com.thoughtworks.feature.Factory.inject trait Foo[A] { @inject def orderingA: Ordering[A] }
When creating a factory for the trait
val factory = Factory[Foo[Int]]
Then the
@inject
method will be replaced to an implicit value.val foo = factory.newInstance() foo.orderingA should be(implicitly[Ordering[Int]])
It will not compile if no implicit value found. For example,
Foo[Symbol]
requires an implicit value of typeOrdering[Symbol]
, which is not availble."Factory[Foo[Symbol]]" shouldNot compile
- Note
The factory may contain abstract parameters of abstract types
trait Outer { trait AbstractParameterApi type AbstractParameter <: AbstractParameterApi trait InnerApi { def foo: AbstractParameter } type Inner <: InnerApi } new Outer { type Inner = InnerApi val innerFactory = Factory[Inner] }
Examples: -
trait
ImplicitApply
[F] extends AnyRef
A dependent type class that apply
F
with implicit values as parameters.A dependent type class that apply
F
with implicit values as parameters.Imports
import com.thoughtworks.feature.ImplicitApply._
This will enable the implicitApply method for any functions
Author:
杨博 (Yang Bo) <[email protected]>
- F
The function type to be implicitly apply
Given a function
f
that requires anOrdering[Int]
val f = { x: Ordering[Int] => "OK" }
Then
f
can implicitly apply as long as its parameter is implicitly available,f.implicitApply should be("OK")
, Given a function
f
that requires an call-by-nameOrdering[Int]
def f0(x: => Ordering[Int]) = "OK" val f = f0 _
Then
f
can implicitly apply as long as its parameter is implicitly available,f.implicitApply should be("OK")
- Note
You can optionally add an implicit modifier on the function parameter.
val f = { implicit x: Ordering[Int] => "OK" } f.implicitApply should be("OK")
It is very useful when you create a curried function.
def g[A] = { (i: A, j: A) => implicit x: Ordering[A] => import x._ if (i > j) { s"$i is greater than $j" } else { s"$i is not greater than $j" } } g(1, 2).implicitApply should be("1 is not greater than 2")
Examples: - trait Mixin [SuperTypes <: HList] extends AnyRef
-
trait
PartialApply
[F, ParameterName <: String with Singleton] extends AnyRef
A dependent type class that bind the specific parameter of
ParameterName
toF
A dependent type class that bind the specific parameter of
ParameterName
toF
Imports
import com.thoughtworks.feature.PartialApply._
This will enable the partialApply method for any functions
Author:
杨博 (Yang Bo) <[email protected]>
- F
The function type to be partially apply
Given a function with three parameters.
val f = { (v1: Int, v2: Int, v3: Int) => (v1 + v2) * v3 }
When partially applying the second parameter.
val partiallyApplied = f.partialApply(v2 = 2)
And applying the rest parameters.
val result = partiallyApplied(v3 = 3, v1 = 1)
Then the result should be the same as applying at once.
result should be(f(1, 2, 3))
, A function with refined parameters can partially apply.
val f: ((Int, Double, String) => String) { def apply(i: Int, d: Double, s: String): String } = { (i, d, s) => (i * d) + s } f.partialApply(s = "seconds").apply(i = 60, d = 1.125) should be(f(s = "seconds", i = 60, d = 1.125))
, Partial applying can be chained.
val f = { (v1: Int, v2: Int, v3: Int) => (v1 + v2) * v3 } f.partialApply(v2 = 2).partialApply(v3 = 3).partialApply(v1 = 1).apply() should be(f(1, 2, 3))
, Function objects can partially apply.
object f extends ((Int, Double, String) => String) { def apply(i: Int, d: Double, s: String): String = { (i * d) + s } } f.partialApply(s = "seconds").apply(i = 60, d = 1.125) should be(f(s = "seconds", i = 60, d = 1.125))
, Case class companion can partially apply.
case class MyCaseClass(i: Int, d: Double, s: String) MyCaseClass.partialApply(s = "seconds").apply(i = 60, d = 1.125) should be(MyCaseClass(s = "seconds", i = 60, d = 1.125))
, Call-by-name functions can partially apply.
def foo(v1: => String, v2: Int) = v1 + v2 val callByNameFunction = foo _ PartialApply.materialize[callByNameFunction.type, "v1"].apply(callByNameFunction, "1").apply(v2 = 2) should be(foo(v1 = "1", v2 = 2))
Examples: - final class The [Widen, Narrow] extends AnyVal
-
class
Untyper
[Universe <: Singleton with Universe] extends AnyRef
Author:
杨博 (Yang Bo) <[email protected]>
-
final
class
include
extends Annotation with StaticAnnotation
An annotation to include code snippets into Scala source file.
An annotation to include code snippets into Scala source file.
Sbt configuration
libraryDependencies += "com.thoughtworks.feature" %% "constructor" % "latest.release" libraryDependencies += scalaOrganization.value % "scala-reflect" % scalaVersion.value % Provided addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.patch)
Author:
杨博 (Yang Bo) <[email protected]>
Given a URL that defines
val i = 42
, when including it intoContainer
.@include("https://gist.github.com/Atry/5dcb1414b804fd7679393cacac3c89fc/raw/5b1748ab6b45c00be0109686fdb25e85cde11ce0/include-example.sc") object Container { def j = i + 1 }
Then
i
should be defined inContainer
.Container.i should be(42)
And other variables inside Container
, like
jshould be able to reference
i.
Container.j should be(Container.i + 1)
Example: