Do i have a misunderstanding how implciits work in Scala - given the following trait in Scala,
trait hasConfig {
implicit def string2array(s: java.lang.String): Array[String] = {
LoadedProperties.getList(s)
}
implicit def string2boolean(s: java.lang.String) : java.lang.Boolean = {
s.toLowerCase() match {
case "true" => true
case "false" => false
}
}
var config: Properties = new Properties()
def getConfigs : Properties = config
def loadConfigs(prop:Properties) : Properties = {
config = prop
config
}
def getConfigAs[T](key:String):T = {
if (hasConfig(key)) {
val value : T = config.getProperty(key).asInstanceOf[T]
value
}
else throw new Exception("Key not found in config")
}
def hasConfig(key: String): Boolean = {
config.containsKey(k)
}
}
Though java.util.properties contains (String, String) key value pairs, I expect the following code to work due to the implicit converstion defined,
class hasConfigTest extends FunSuite {
val recModel = new Object with hasConfig
//val prop = LoadedProperties.fromFile("test") Read properties from some file
recModel.loadConfigs(prop)
test("test string paramater") {
assert(recModel.getConfigAs[String]("application.id").equals("framework"))
}
test("test boolean paramater") {
assert(recModel.getConfigAs[Boolean]("framework.booleanvalue") == true)
//Property file contains framework.booleanvalue=true
//expected to return java.lang.boolean, get java.lang.string
}
}
However, I get the following error,
java.lang.String cannot be cast to java.lang.Boolean
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Boolean
Why is the implcit conversion not taking care of this?
It doesn't work because casting (asInstanceOf) is something entirely different than implicit conversions. There are multiple ways in which you can solve this.
Implicit conversion
If you want to use the hardcore implicit conversions magic you should rewrite you getConfigAs method like this:
def getConfig(key:String): String = {
if (hasConfig(key)) {
val value: String = config.getProperty(key)
value
}
else throw new Exception("Key not found in config")
}
You will have to import the conversions into the current scope when you use getConfig.
val recModel = new Object with hasConfig
import recModel._
recModel.loadConfigs(prop)
val value: Boolean = recModel.getConfig("framework.booleanvalue")
Implicit parameters
A better way would be to keep your current API, but then you will have to introduce an implicit parameter because the implementation of getConfigAs needs access to the conversion.
def getConfigAs[T](key:String)(implicit conv: String => T): T = {
if (hasConfig(key)) {
val value: String = config.getProperty(key)
value
}
else throw new Exception("Key not found in config")
}
You will still need to import the necessary conversions at the use site though.
val recModel = new Object with hasConfig
import recModel._
recModel.loadConfigs(prop)
val value = recModel.getConfigAs[Boolean]("framework.booleanvalue")
Typeclasses
A way to avoid having to import your conversions (and possibly implicitly converting all kinds of Strings by accident) is to introduce a new type to encode your conversions. Then you can implement the conversions in its companion object, where implicit search can find them without importing them.
trait Converter[To]{
def convert(s: String): To
}
object Converter {
implicit val string2array: Converter[Array[String]] = new Converter[Array[String]] {
def convert(s: String): Array[String] =
LoadedProperties.getList(s)
}
implicit val string2boolean: Converter[Boolean] = new Converter[Boolean] {
def convert(s: String): Boolean =
s.toLowerCase() match {
case "true" => true
case "false" => false
}
}
}
Then you can change your getConfigAs method.
def getConfigAs[T](key:String)(implicit conv: Converter[T]): T = {
if (hasConfig(key)) {
val value: String = config.getProperty(key)
conv.convert(value)
}
else throw new Exception("Key not found in config")
}
And use it.
val recModel = new Object with hasConfig
recModel.loadConfigs(prop)
val value = recModel.getConfigAs[Boolean]("framework.booleanvalue")
You might also want to take a look over here.
Implicit conversions should be defined in scope, for example in enclosing object or imported into the current scope. In your case they should be defined in scope of the hasConfigTest class.
http://docs.scala-lang.org/tutorials/FAQ/finding-implicits
Here's a simple reproducible example:
object m {
implicit def string2boolean(s: String): Boolean = {
s.toLowerCase() match {
case "true" => true
case "false" => false
}
} //> string2boolean: (s: String)Boolean
println(false || "true") //> true
println(false || "false") //> false
}
I think what you are trying to say, is something like this:
import java.util.Properties
object LoadedProperties {
def getList(s: String): Array[String] = Array.empty
}
object hasConfig {
sealed trait ConfigReader[T] {
def read(conf: String): T
}
implicit object BooleanConfigReader extends ConfigReader[Boolean] {
override def read(conf: String): Boolean = conf.toLowerCase() match {
case "true" => true
case "false" => false
}
}
implicit object ArrayConfigReader extends ConfigReader[Array[String]] {
override def read(s: String): Array[String] = {
LoadedProperties.getList(s)
}
}
var config: Properties = new Properties()
def getConfigs: Properties = config
def loadConfigs(prop: Properties): Properties = {
config = prop
config
}
def getConfigAs[T](key: String)(implicit reader: ConfigReader[T]): T = {
val prop = config.getProperty(key)
if (prop == null)
throw new Exception("Key not found in config")
reader.read(prop)
}
}
val props = new Properties()
props.setProperty("a", "false")
props.setProperty("b", "some")
hasConfig.loadConfigs(props)
hasConfig.getConfigAs[Boolean]("a")
hasConfig.getConfigAs[Array[String]]("a")
Related
I would like to implement an akka Serializer using upickle but I'm not sure its possible. To do so I would need to implement a Serializer something like the following:
import akka.serialization.Serializer
import upickle.default._
class UpickleSerializer extends Serializer {
def includeManifest: Boolean = true
def identifier = 1234567
def toBinary(obj: AnyRef): Array[Byte] = {
writeBinary(obj) // ???
}
def fromBinary(bytes: Array[Byte], clazz: Option[Class[_]]): AnyRef = {
readBinary(bytes) // ???
}
}
The problem is I cannot call writeBinary/readBinary without having the relevant Writer/Reader. Is there a way I can look these up based on the object class?
Take a look at following files, you should get some ideas!
CborAkkaSerializer.scala
LocationAkkaSerializer.scala
Note: These serializers are using cbor
I found a way to do it using reflection. I base the solution on the assumption that any object that needs to be serialized should have defined a ReadWriter in its companion object:
class UpickleSerializer extends Serializer {
private var map = Map[Class[_], ReadWriter[AnyRef]]()
def includeManifest: Boolean = true
def identifier = 1234567
def toBinary(obj: AnyRef): Array[Byte] = {
implicit val rw = getReadWriter(obj.getClass)
writeBinary(obj)
}
def fromBinary(bytes: Array[Byte], clazz: Option[Class[_]]): AnyRef = {
implicit val rw = lookup(clazz.get)
readBinary[AnyRef](bytes)
}
private def getReadWriter(clazz: Class[_]) = map.get(clazz) match {
case Some(rw) => rw
case None =>
val rw = lookup(clazz)
map += clazz -> rw
rw
}
private def lookup(clazz: Class[_]) = {
import scala.reflect.runtime._
val rootMirror = universe.runtimeMirror(clazz.getClassLoader)
val classSymbol = rootMirror.classSymbol(clazz)
val moduleSymbol = classSymbol.companion.asModule
val moduleMirror = rootMirror.reflectModule(moduleSymbol)
val instanceMirror = rootMirror.reflect(moduleMirror.instance)
val members = instanceMirror.symbol.typeSignature.members
members.find(_.typeSignature <:< typeOf[ReadWriter[_]]) match {
case Some(rw) =>
instanceMirror.reflectField(rw.asTerm).get.asInstanceOf[ReadWriter[AnyRef]]
case None =>
throw new RuntimeException("Not found")
}
}
}
class Test(a: String, b: Array[String], c: Array[String]){
def this(b: Array[String], c: Array[String]) {
this("1", b, c)
}
def this() = {
this(null, null, null)
}
}
I have a class Test like above, and I would like use scala reflection to invoke one of them
I try to use the follow code
import scala.reflect.runtime.{universe => ru}
val clsTest = ru.typeOf[Test].typeSymbol.asClass
val cm = m.reflectClass(clsTest)
val ctor = ru.typeOf[Test].decl(ru.termNames.CONSTRUCTOR).asTerm.alternatives.map(_.asMethod)
but I don't know how to select the method based on method signature.
Is there any approach to select the method based on the type signature like java reflect code? Thanks!
I have read the scala doc about reflect, but it don't solve my problem. it has only one constructor method. scala reflect doc
I have found a approach to filter by parameter types
methodName: means which method you want to reflect
allScope: true means find from this and super, false means find only from this
types: is the parameter types, you can use Seq(typeOf[T1], typeOf[T2]):_*
x: is the instance which will be reflected
the key is we can get method parameter types by methodSymbol.paramLists.head.map(_.info)
val ru: scala.reflect.runtime.universe.type = scala.reflect.runtime.universe
def m: ru.Mirror = {
ru.runtimeMirror(Thread.currentThread().getContextClassLoader)
}
//reflect method, method can't be curry
def reflectMethod[T: ru.TypeTag : ClassTag](methodName: String, allScope: Boolean, types: ru.Type*)(x: T): ru.MethodMirror = {
val instanceMirror = m.reflect(x)
val methodSymbols = if (allScope) {
val members = getTypeTag(x).tpe.member(ru.TermName(methodName))
if (members.equals(ru.NoSymbol)) {
throw new NoSuchMethodException(noSuchMethodException(methodName, allScope, types: _*)(x))
}
members
.asTerm
.alternatives
.map(_.asMethod)
} else {
val decls = getTypeTag(x).tpe.decl(ru.TermName(methodName))
if (decls.equals(ru.NoSymbol)) {
throw new NoSuchMethodException(noSuchMethodException(methodName, allScope, types: _*)(x))
}
decls
.asTerm
.alternatives
.map(_.asMethod)
}
methodSymbols.foreach(item => assert(item.paramLists.size < 2, "we don't support curry method yet"))
val methodSymbol = methodSymbols.find(item =>
if (item.paramLists.head.isEmpty) {
types.isEmpty
} else {
if (types.isEmpty) {
item.paramLists.head.isEmpty
} else {
// empty forall is true
item.paramLists.head.zip(types).forall(pair => pair._1.info =:= pair._2)
}
}).getOrElse(throw new NoSuchMethodException(noSuchMethodException(methodName, allScope, types: _*)(x)))
val methodMirror = instanceMirror.reflectMethod(methodSymbol)
methodMirror
}
private def noSuchMethodException[T: ru.TypeTag : ClassTag](methodName: String, allScope: Boolean, types: ru.Type*)(x: T): String = {
s"no such method: $methodName, allScope: $allScope type: $types in ${getRuntimeClass(x)}"
}
// the method as List(list(a,b,c))
// and this is PrimaryConstructor
class Test(a: String, b: Array[String], c: Array[String]) {
// the method as List(list(b,c))
def this(b: Array[String], c: Array[String]) {
this("1", b, c)
}
// the method as List(list())
def this() = {
this(null, null, null)
}
// the method as List(list(a),list(b,c)
def this(a:String )(b:String,c:String ){
this(null,null,null)
}
}
val constructor = typeOf[Test].members
// filter all constructor
.filter(e => e.isConstructor).map(e => e.asMethod)
// find which are you want
// edit 1
.find( e =>{
val methodParamsType = e.paramLists.head.map(e =>e.typeSignature)
// what params type are you
val expectParamsType = List(typeOf[Array[String]],typeOf[Array[String]])
methodParamsType.length == expectParamsType.length &&
methodParamsType.zip(expectParamsType).forall{case (l,r)=>l =:= r }
})
// or
// .find(e=>e.isPrimaryConstructor)
// .find(e=>e.paramLists.head.length == 2)
.get
So I am attempting to grab the types of each field in a Scala object class:
package myapp.model
object MyObject {
val theInt: Option[Int]
}
Using the ReflectionHelper so graciously provided by Brian in this post. I use getFieldType but it returns Option[Object] instead of what it is, which is Option[Int]. The example code in that answer works for a case class, for example:
package myapp.model
case class Person(
name: String,
age: Option[Int]
)
scala> ReflectionHelper.getFieldType("myapp.model.Person", "age") // int
res12: Option[reflect.runtime.universe.Type] = Some(Option[Int])
However, if I run getFieldType on a Scala object field, we get this:
scala> ReflectionHelper.getFieldType("myapp.model.MyObject$", "theInt")
res10: Option[reflect.runtime.universe.Type] = Some(Option[Object])
What is different about Scala objects that causes this behavior and how can I get getFieldType to return Option[Int] instead of Option[Object] like it does for the case class?
Here is the ReflectionHelper from the other question for convenience:
import scala.reflect.runtime.{ universe => u }
import scala.reflect.runtime.universe._
object ReflectionHelper {
val classLoader = Thread.currentThread().getContextClassLoader
val mirror = u.runtimeMirror(classLoader)
def getFieldType(className: String, fieldName: String): Option[Type] = {
val classSymbol = mirror.staticClass(className)
for {
fieldSymbol <- classSymbol.selfType.members.collectFirst({
case s: Symbol if s.isPublic && s.name.decodedName.toString() == fieldName => s
})
} yield {
fieldSymbol.info.resultType
}
}
def maybeUnwrapFieldType[A](fieldType: Type)(implicit tag: TypeTag[A]): Option[Type] = {
if (fieldType.typeConstructor == tag.tpe.typeConstructor) {
fieldType.typeArgs.headOption
} else {
Option(fieldType)
}
}
def getFieldClass(className: String, fieldName: String): java.lang.Class[_] = {
// case normal field return its class
// case Option field return generic type of Option
val result = for {
fieldType <- getFieldType(className, fieldName)
unwrappedFieldType <- maybeUnwrapFieldType[Option[_]](fieldType)
} yield {
mirror.runtimeClass(unwrappedFieldType)
}
// Consider changing return type to: Option[Class[_]]
result.getOrElse(null)
}
}
Try
import scala.reflect.runtime.universe._
import scala.reflect.runtime
val runtimeMirror = runtime.currentMirror
runtimeMirror.staticClass("myapp.model.Person").typeSignature
.member(TermName("age")).typeSignature // => Option[Int]
runtimeMirror.staticModule("myapp.model.MyObject").typeSignature
.member(TermName("theInt")).typeSignature // => Option[Int]
Can't I use a generic on the unapply method of an extractor along with an implicit "converter" to support a pattern match specific to the parameterised type?
I'd like to do this (Note the use of [T] on the unapply line),
trait StringDecoder[A] {
def fromString(string: String): Option[A]
}
object ExampleExtractor {
def unapply[T](a: String)(implicit evidence: StringDecoder[T]): Option[T] = {
evidence.fromString(a)
}
}
object Example extends App {
implicit val stringDecoder = new StringDecoder[String] {
def fromString(string: String): Option[String] = Some(string)
}
implicit val intDecoder = new StringDecoder[Int] {
def fromString(string: String): Option[Int] = Some(string.charAt(0).toInt)
}
val result = "hello" match {
case ExampleExtractor[String](x) => x // <- type hint barfs
}
println(result)
}
But I get the following compilation error
Error: (25, 10) not found: type ExampleExtractor
case ExampleExtractor[String] (x) => x
^
It works fine if I have only one implicit val in scope and drop the type hint (see below), but that defeats the object.
object Example extends App {
implicit val intDecoder = new StringDecoder[Int] {
def fromString(string: String): Option[Int] = Some(string.charAt(0).toInt)
}
val result = "hello" match {
case ExampleExtractor(x) => x
}
println(result)
}
A variant of your typed string decoder looks promising:
trait StringDecoder[A] {
def fromString(s: String): Option[A]
}
class ExampleExtractor[T](ev: StringDecoder[T]) {
def unapply(s: String) = ev.fromString(s)
}
object ExampleExtractor {
def apply[A](implicit ev: StringDecoder[A]) = new ExampleExtractor(ev)
}
then
implicit val intDecoder = new StringDecoder[Int] {
def fromString(s: String) = scala.util.Try {
Integer.parseInt(s)
}.toOption
}
val asInt = ExampleExtractor[Int]
val asInt(Nb) = "1111"
seems to produce what you're asking for. One problem remains: it seems that trying to
val ExampleExtractor[Int](nB) = "1111"
results in a compiler crash (at least inside my 2.10.3 SBT Scala console).
I'm having some problems with a macro I've written to help me log metrics represented as case class instances to to InfluxDB. I presume I'm having a type erasure problem and that the tyep parameter T is getting lost, but I'm not entirely sure what's going on. (This is also my first exposure to Scala macros.)
import scala.language.experimental.macros
import play.api.libs.json.{JsNumber, JsString, JsObject, JsArray}
abstract class Metric[T] {
def series: String
def jsFields: JsArray = macro MetricsMacros.jsFields[T]
def jsValues: JsArray = macro MetricsMacros.jsValues[T]
}
object Metrics {
case class LoggedMetric(timestamp: Long, series: String, fields: JsArray, values: JsArray)
case object Kick
def log[T](metric: Metric[T]): Unit = {
println(LoggedMetric(
System.currentTimeMillis,
metric.series,
metric.jsFields,
metric.jsValues
))
}
}
And here's an example metric case class:
case class SessionCountMetric(a: Int, b: String) extends Metric[SessionCountMetric] {
val series = "sessioncount"
}
Here's what happens when I try to log it:
scala> val m = SessionCountMetric(1, "a")
m: com.confabulous.deva.SessionCountMetric = SessionCountMetric(1,a)
scala> Metrics.log(m)
LoggedMetric(1411450638296,sessioncount,[],[])
Even though the macro itself seems to work fine:
scala> m.jsFields
res1: play.api.libs.json.JsArray = ["a","b"]
scala> m.jsValues
res2: play.api.libs.json.JsArray = [1,"a"]
Here's the actual macro itself:
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
object MetricsMacros {
private def fieldNames[T: c.WeakTypeTag](c: Context)= {
val tpe = c.weakTypeOf[T]
tpe.decls.collect {
case field if field.isMethod && field.asMethod.isCaseAccessor => field.asTerm.name
}
}
def jsFields[T: c.WeakTypeTag](c: Context) = {
import c.universe._
val names = fieldNames[T](c)
Apply(
q"play.api.libs.json.Json.arr",
names.map(name => Literal(Constant(name.toString))).toList
)
}
def jsValues[T: c.WeakTypeTag](c: Context) = {
import c.universe._
val names = fieldNames[T](c)
Apply(
q"play.api.libs.json.Json.arr",
names.map(name => q"${c.prefix.tree}.$name").toList
)
}
}
Update
I tried Eugene's second suggestion like this:
abstract class Metric[T] {
def series: String
}
trait MetricSerializer[T] {
def fields: Seq[String]
def values(metric: T): Seq[Any]
}
object MetricSerializer {
implicit def materializeSerializer[T]: MetricSerializer[T] = macro MetricsMacros.materializeSerializer[T]
}
object Metrics {
def log[T: MetricSerializer](metric: T): Unit = {
val serializer = implicitly[MetricSerializer[T]]
println(serializer.fields)
println(serializer.values(metric))
}
}
with the macro now looking like this:
object MetricsMacros {
def materializeSerializer[T: c.WeakTypeTag](c: Context) = {
import c.universe._
val tpe = c.weakTypeOf[T]
val names = tpe.decls.collect {
case field if field.isMethod && field.asMethod.isCaseAccessor => field.asTerm.name
}
val fields = Apply(
q"Seq",
names.map(name => Literal(Constant(name.toString))).toList
)
val values = Apply(
q"Seq",
names.map(name => q"metric.$name").toList
)
q"""
new MetricSerializer[$tpe] {
def fields = $fields
def values(metric: Metric[$tpe]) = $values
}
"""
}
}
However, when I call Metrics.log -- specifically when it calls implicitly[MetricSerializer[T]] I get the following error:
error: value a is not a member of com.confabulous.deva.Metric[com.confabulous.deva.SessionCountMetric]
Why is it trying to use Metric[com.confabulous.deva.SessionCountMetric] instead of SessionCountMetric?
Conclusion
Fixed it.
def values(metric: Metric[$tpe]) = $values
should have been
def values(metric: $tpe) = $values
You're in a situation that's very close to one described in a recent question: scala macros: defer type inference.
As things stand right now, you'll have to turn log into a macro. An alternative would also to turn Metric.jsFields and Metric.jsValues into JsFieldable and JsValuable type classes materialized by implicit macros at callsites of log (http://docs.scala-lang.org/overviews/macros/implicits.html).