package feature
- Alphabetic
- Public
- All
Type Members
-
trait
ByName[ByNameType] extends Serializable
A type class for convert between by-name type and normal by-value type.
A type class for convert between by-name type and normal by-value type.
Installation
libraryDependencies += "com.thoughtworks.feature" %% "byname" % "latest.release"
This ByName type class provide an extension method extract for ByName.=> types via implicit view, which can be import as follow:
import com.thoughtworks.feature.ByName.ops._
Author:
杨博 (Yang Bo) <[email protected]>
- ByNameType
The by-name type
- Note
When using this ByName type class, you should make sure the type parameter
ByNameType
is an abstract type, in case of Scala compiler bugs. You can use a trick similar to C++'s Pimpl Idiom to create opacity abstract typesimport com.thoughtworks.feature.ByName.`=>` trait OpacityTypes { type IntByName implicit def typeClass: ByName.Aux[IntByName, Int] } val opacityTypes: OpacityTypes = new OpacityTypes { type IntByName = `=>`[Int] override def typeClass = ByName[IntByName] } import opacityTypes._
Now
IntByName
is an abstract type with underlying=> Int
type.
Given a by-name parameter of the return value ofgetInt()
,val getInt = stubFunction[Int] val boxedByName: IntByName = typeClass.make(getInt())
when
getInt
returns 42,getInt.when.once returns 42
then the value of the the by-name parameter should be 42;
boxedByName.extract should be(42)
when
getInt
returns 144,getInt.when.once returns 144
then the value of the the by-name parameter should be 144.
boxedByName.extract should be(144)
-
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 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._
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 other than scala.Any 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 Serializable
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 abstract members.
trait Foo { val myValue: String var myVariable: Long def myMethod0(): Option[Double] def myMethod2(p0: Int, p1: Int): Int def myCurriedMethod(seq: Seq[Int])(mapper: Int => String): Seq[String] // Methods without pararmeters is disabled for now, due to https://github.com/scala/bug/issues/10647 // def myByNameMethod: Option[Int] }
When creating a factory for the trait.
val factory = Factory[Foo]
Then the newInstance method of the factory should accept named arguments according to abstract members.
val createdFromNamedArguments: Foo = factory.newInstance( myValue = "string value", myVariable = 42L, myMethod0 = () => Some(0.5), myMethod2 = _ + _, myCurriedMethod = seq => mapper => seq.map(mapper) ) createdFromNamedArguments.myValue should be("string value") createdFromNamedArguments.myVariable should be(42L) createdFromNamedArguments.myMethod0() should be(Some(0.5)) createdFromNamedArguments.myMethod0() shouldNot be theSameInstanceAs createdFromNamedArguments.myMethod0() createdFromNamedArguments.myMethod2(1000, 24) should be(1024) createdFromNamedArguments.myCurriedMethod(Seq(2, 3, 4))(_.toString) should be(Seq("2", "3", "4"))
When using unnamed parameters, the parameters should be passed in alphabetical order
val createdFromUnnamedArguments: Foo = factory.newInstance( seq => mapper => seq.map(mapper), // myCurriedMethod () => Some(0.5), // myMethod0 _ + _, // myMethod2 "string value", // myValue 42L // myVariable )
, 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
If the trait does not contain abstract methods other than
@inject
methods, then the factory type class is a Factory.Factory0, which can be summoned by Predef.implicitly,val nullaryFactory = implicitly[Factory.Factory0[Foo[Int]]]
and newInstance method is available on the Factory.Factory0 as well.
nullaryFactory.newInstance().orderingA should be(implicitly[Ordering[Int]])
- Note
However, if the nested type is an alias to another type outside of the type to create, then it is allowed
trait Outer { type Inner = String val inner: Option[Inner] } val outer: Outer = Factory[Outer].newInstance(inner = Some("my value")) outer.inner should be(Some("my value"))
,This Factory disallows creating types that has an abstract member whose type depends on nested types. Given an abstract
val inner
whose type is theInner
,trait Outer { trait Inner val inner: Option[Inner] }
then the attempt to create factory for
,Outer
, likeFactory[Outer].newInstance(inner = None)
, will cause avalue inner has incompatible type
compile error.@inject works on implicit abstract methods as well.
import com.thoughtworks.feature.Factory.inject trait Foo[A] { @inject implicit def orderingA: Ordering[A] } Factory[Foo[Int]].newInstance().orderingA should be(implicitly[Ordering[Int]])
,Factories may be nested
import com.thoughtworks.feature.Factory.inject import com.thoughtworks.feature.Factory.Factory1 import com.thoughtworks.feature.ByName.`=>` trait Outer { trait AbstractParameterApi type AbstractParameter <: AbstractParameterApi trait InnerApi { def foo: AbstractParameter } type Inner <: InnerApi @inject val innerFactory: Factory1[`=>`[AbstractParameter], Inner] } val outer = Factory[Outer].newInstance() outer.innerFactory.newInstance(new outer.AbstractParameterApi {}) should be(an[outer.Inner])
,Factories may create types that contains refinement types
trait SomeBuilder { type A def makeSome(a: A) = Some(a) } val someBuilder = Factory[SomeBuilder { type A = Int }].newInstance() someBuilder.makeSome(42) should be(Some(42))
Examples: -
trait
Glb[Mixin] extends Serializable
A type class that converts a mix-in type to the greatest lower bound of the components of the mix-in.
A type class that converts a mix-in type to the greatest lower bound of the components of the mix-in.
This Glb type class provides an extension method glb via implicit view, which can be import as follow:
import com.thoughtworks.feature.Glb.ops._
Author:
杨博 (Yang Bo) <[email protected]>
Given a mix-in of two refinement components,
type A = Option[Any] { def get: Immutable } type B = Option[Any] { def get: Iterable[String] } val ab: A with B = Some(List("cool"))
when convert it to its greatest lower bound,
val glbOfAB = toAllGlbOps(ab).glb
Then the common method should return the greatest lower bound of each components's return type.
val result: Immutable with Iterable[String] = glbOfAB.get result should be(List("cool"))
- Note
This Glb type class is implemented via scala.reflect.api.Types.glb, which is similar to intersection operator in Dotty, except this Glb works for refinement type only.
,The implicit conversion toAllGlbOps must be explicitly invoked, as a workaround of https://github.com/scala/bug/issues/10671
Example: -
trait
ImplicitApply[F] extends Serializable
A dependent type class that invokes
F
with implicit values as parameters.A dependent type class that invokes
F
with implicit values as parameters.Imports
import com.thoughtworks.feature.ImplicitApply.ops._
This will enable the implicitApply method for any functions
Author:
杨博 (Yang Bo) <[email protected]>
- F
The function type to be implicitly invoked
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[L <: HList] extends AnyRef
A type class that converts a shapeless.HList to a mix-in type.
A type class that converts a shapeless.HList to a mix-in type.
Common imports
You may want to use Mixin with shapeless.HList.
import shapeless._
-
trait
PartialApply[F, ParameterName <: String with Singleton] extends Serializable
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.ops._
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: -
trait
SelfType[A] extends AnyRef
A type class that extracts the self-type of
A
.A type class that extracts the self-type of
A
.Given a compound type,
trait MySelfType1 trait MyTrait1 { this: MySelfType1 => } trait MyTrait2 trait MySelfType3 trait MyTrait3 { this: MySelfType3 => } type MyCompoundType = MyTrait1 with MyTrait2 with MyTrait3 { type MyRefinement = Int }
then the self type should be a compound type of each mix-in part,
val selfType = SelfType[MyCompoundType] "implicitly[selfType.Out =:= (MySelfType1 with MySelfType3 with MyTrait1 with MyTrait2 with MyTrait3)]" should compile
and the self type should not contain the refinement statements
"implicitly[selfType.Out <:< { type MyRefinement = Int }]" shouldNot typeCheck
, Given a trait with an explicit self-type declaration,
trait MySelfType trait MyTrait { this: MySelfType => }
then the self type should be itself with the type of
this
.val selfType = SelfType[MyTrait] "implicitly[selfType.Out =:= (MySelfType with MyTrait)]" should compile
, Given a trait without an explicit self-type declaration
trait MyTrait
Then the self type should be itself
val selfType = SelfType[MyTrait] "implicitly[selfType.Out =:= MyTrait]" should compile
Examples: -
trait
Structural[Mixin] extends Serializable
A type class that converts a class type to a refinement type
A type class that converts a class type to a refinement type
This Structural type class provides an extension method structuralize via implicit view, which can be import as follow:
import com.thoughtworks.feature.Structural.ops._
Given a class that contains abstract types:
trait Container extends Iterable[String] { trait InnerApi type Inner <: InnerApi def inner: Inner = ??? type Self0 >: this.type def self: Self0 = this } val container: Container = new Container { def iterator = Iterator.empty }
When converting it to a structural type
import scala.language.higherKinds val myStruct = container.structuralize
Then methods on the converted struct should be able to access via reflective call
import scala.language.reflectiveCalls myStruct.self should be(myStruct)
, Given a String
val myString: String = "foo"
When converting it to a structural type
val myStruct = myString.structuralize
Then methods on the converted struct should be able to access via reflective call
import scala.language.reflectiveCalls myStruct.startsWith("f") should be(true)
However, the struct is not a String
"myStruct: String" shouldNot typeCheck
Examples: -
final
class
The[Widen, Narrow <: Widen] extends AnyVal
A helper that summons dependent type class
Widen
.A helper that summons dependent type class
Widen
.- Note
The
can be used as an alternative to implicitly for dependent type classes.val narrowed: Int <:< Int = The[Int <:< Any].value
- See also
https://github.com/milessabin/shapeless/pull/695 for discussion about the motivation of
The
.
-
class
Untyper[Universe <: Singleton with Universe] extends AnyRef
A utility to convert Type to Tree.
Author:
杨博 (Yang Bo) <[email protected]>
- Note
The primary intent of Untyper class is for some macro libraries in this feature.scala project, although it may be also useful for other projects.
Value Members
- object ByName extends Serializable
- object Caller
- object Constructor
- object Demixin
- object Factory extends LowPriorityFactory with Serializable
- object Glb extends Serializable
- object ImplicitApply extends Serializable
- object Mixin extends LowPriorityMixin
- object PartialApply extends Serializable
- object SelfType
- object Structural extends Serializable
- object The