I'm quite used to Typescript's function-with-param-names as function-params:
function doHello(
helloFunc: (lastName: string, firstName: string) => void,
...
) { ... }
Here, helloFunc describes a function-as-param with 'named' params (lastName, firstName)
but I could only find examples without param-names, such as:
case class HelloWoot(
helloFunc: (String, String) => Unit,
...
)
which omits some info about helloFunc signature.
So, how do I get the following code to compile in Scala?
case class HelloWoot(
helloFunc: (lastName: String, firstName: String) => Unit,
// Error
)
It's not possible to provide named parameters in a higher order function. If you are worried about people mixing up the String parameters you could introduce a new type like this:
// New object with firstName and lastName
case class NamesObject(firstName: String, lastName: String)
// Higher order function now takes the new type as input
case class HelloWoot(helloFunc: (NamesObject) => Unit)
// You can now safely access the correct variable without having to rely on their order in the Tuple2
HelloWoot(a => println(a.firstName, a.lastName))
Related
I have a case class defined for JSON parsing using the Circe library:
final case class Event(
source: Option[String],
nonce: Option[Int],
`type`: Option[Any],
tag: Option[String],
payload: Option[Any],
blockOrder: Option[Int] = None,
metadata: ResultMetadata[OperationResult.Event]
) extends Operation
Note that one of the fields is called type and I can only use the name using backticks or I get a compiler error. I don't have any choice over the schema used by the source system supplying the data.
Later on, when I try to pattern match on the case class in order to write some information to a database...
private val convertEvent: PartialFunction[
(Block, OperationHash, Operation),
Tables.OperationsRow
] = {
case (
block,
groupHash,
Event(source, nonce, `type`, tag, payload, blockOrder, metadata)
) =>
...
...I get the following error:
not found: value type
Event(source, nonce, `type`, tag, payload, blockOrder, metadata).
Is there any way I can still use Circe and case classes in general when a field has the same name as a reserved keyword?
In 2.13.8 the error is
not found: value type
Identifiers enclosed in backticks are not pattern variables but match the value in scope.
Event(source, nonce, `type`, tag, payload, blockOrder, metadata)
so just use something different from type in pattern matching as a pattern variable.
For example
case (
block,
groupHash,
Event(source, nonce, typ, tag, payload, blockOrder, metadata)
) =>
That's because backticks in pattern matching mean to use an already-existing value accessible in the scope, to check the equity of a pattern variable, like this:
case class Person(name: String, age: Int)
val johnName = "John"
val john = Person(johnName, 99)
val alice = Person("Alice", 128)
john match {
case Person(`johnName`, someAge) =>
// this will be matched, sine johnName = "John"
}
alice match {
case Person(`johnName`, someAge) =>
// won't match, "Alice" != "John"
case Person(johnName, someAge) =>
// this one matches, since johnName in the pattern is only some alias,
// and will shadow the variable defined above
}
The solution/workaround is already mentioned in the other anwer.
I have the following case class.
case class CustomAttributeInfo[T,Y](
attribute:MyAttribute[_],
fieldName:String,
valueParser:T => Y){}
The case class takes three values.
The last argument is a function that will parse an input of any type and return the part of the input we wish to keep.
(Imagine, for just one example, I pass in a jsonstring, convert to json object, and extract an Int).
The companion object will supply a range of functions that we can pass to the case class. The one shown here, simply takes the input as a string and returns it as a string (the most simple possible example).
object CustomAttributeInfo {
val simpleString = (s:String) => s
}
I create the case class as follows:
CustomAttributeInfo(MyAttribute(var1, var2), name, CustomAttributeInfo.simpleString)
Later, I call the function 'valueParser'
customAttributeInfo.valueParser(k)
Compilation error
Error:(366, 69) type mismatch;
found : k.type (with underlying type String)
required: _$13
case Some(info) => Some((info.attribute, info.valueParser(k)))
I am not a generics expert (obviously). I have done some reading, but I have not seen a discussion about a case like this. Any advice and explanation would be most welcome
You haven't provide enough information to answer your question.
The following code compiles.
If you still have compile error provide MCVE.
case class MyAttribute[_](var1: Any, var2: Any)
case class CustomAttributeInfo[T,Y](attribute:MyAttribute[_], fieldName:String, valueParser:T => Y) {}
object CustomAttributeInfo {
val simpleString = (s:String) => s
}
val var1: Any = ???
val var2: Any = ???
val name: String = ???
val customAttributeInfo = CustomAttributeInfo(MyAttribute(var1, var2), name, CustomAttributeInfo.simpleString)
val k = "abc"
customAttributeInfo.valueParser(k)
#Dmytro was right that a simple example compiled. In my actual codebase code, however, we needed to be specific about the type.
This worked:
object CustomAttributeInfo {
type T = Any
val simpleString = (s:T) => s.toString
}
Basically I want to read untyped JSON into a type that's specified by a string. Pseudocode.
def getObject(json: Json, typeString: String): typeOf(typeString) = extract[typeOf(typeString)](json)
typeOf is just some random thing that gives a type from a string.
I would say that it is impossible without runtime reflection. With runtime reflection I would try the following:
get the Class[_] by its name from ClassLoader - it would only work if you specified full name (with packages and everything) in String,
then use something like Jackson which uses runtime reflection to (de)serialize things new ObjectMapper().readValue(json, obtainedClass),
obviously return type would be Any - theoretically you could use path-dependent types here, but personally I see little benefit.
However:
it would be pretty fragile - any mismatch between JSON and class and it fails,
you would have to pass full name of a class (again, fragile) - if you passed data from outside, that sounds like a potential security issue, if you passing data internally... why not using type class?
if you need it for persistence... then you could persist it together with a discriminator, which you could use to provide right type class. (After all set of classes that you serialize is finite and could easily be traced). Then type safe approach with e.g. Circe would be possible.
Well I guess you could also change signature into:
def getObject[T: ClassTag](json: Json): T
or
def getObject[T](json: Json, clazz: Class[T]): T
and be sure that function return what you want. Getting Class[_] by its name and passing it reduces us to original solution.
EDIT:
To show an example of how to extract type from discriminator (pseudocode):
// store discriminator in DB
// use it to deserialize and dispatch with predefined function
def deserializeAndHandle(discriminator: String, json: String): Unit = discriminator match {
case "my.package.A" => decode[my.package.A](json).map(handlerForA)
case "my.package.B" => decode[my.package.B](json).map(handlerForB)
case "my.package.C" => decode[my.package.C](json).map(handlerForC)
case _ =>
}
deserializeAndHandle(discriminator, json)
// store discriminator in DB
// use it to deserialize to Object which can be pattern-matched later
def deserializeToObject(discriminator: String, json: String): Option[Any] = discriminator match {
case "my.package.A" => decode[my.package.A](json).toOption
case "my.package.B" => decode[my.package.B](json).toOption
case "my.package.C" => decode[my.package.C](json).toOption
case _ => None
}
deserializeToObject(discriminator, json) map {
case a : A => ...
case b : B => ...
case c : C => ...
} getOrElse ???
// wrap unrelated types with sealed trait to make it sum type
// use sum type support of Circe
sealed trait Envelope
final case class EnvelopeA(a: A) extends Envelope
final case class EnvelopeB(b: B) extends Envelope
final case class EnvelopeA(c: C) extends Envelope
def deserializeEnveloped(json): Option[Envelope] = decode[Envelope](json).toOption
deserializeEnveloped(json) map {
case EnvelopeA(a) => ...
case EnvelopeB(b) => ...
case EnvelopeC(c) => ...
} getOrElse ???
I'm new to scala.
What does following syntax mean?
case class User(val id: Long)(val username: String)
I've read about currying in scala, but explain please how it related to construction above (if related).
Thanks.
Just like partially-applied functions, a constructor (which is a function from its arguments to the constructed type) can be partially applied, in this case:
scala> case class User(val id: Long)(val username: String)
defined class User
scala> val userBuilder = User(123L) _
userBuilder: String => User = <function1>
Note the type of the resulting userBuilder - it's a function from String (the remaining parameter) to User
Now, like partially applied functions, you can apply this result to a String and get a User instance:
scala> val user = userBuilder("a")
user: User = User(123)
scala> user.username
res1: String = a
When is this useful?
When you want to construct many instances with common values for a subset of the arguments, e.g.:
case class Person(lastName: String)(val firstName: String)
class Family(lastName: String, firstNames: List[String]) {
def getMembers: List[Person] = {
val creator = Person(lastName) _ // will be reused for each first name!
firstNames.map(creator)
}
}
When you want to use one argument as the default value of another one:
case class Measure(min: Int)(val max: Int = min*2)
Measure(5)() // max = 10
Measure(5)(12) // default overridden, max = 12
When you want to use implicit arguments, which must reside in a separate, last argument list of the function, as described int the Scala Language Specification (Chapter 7.2):
A method or constructor can have only one implicit
parameter list, and it must be the last parameter list given.
It allows you to construct the object in steps.
val user = User(123L) _ // user now has only the ID
// later on
val completeUser = user("moreo") // user now also has the username
This is generally useful when you want to have your object follow an interface, but need to pass additional parameters, so you first initialise your object with those parameters and then later you get a function that can follow the interface.
How do i define generic parameter "on the fly"?
Example:
I have some method def get[T](name: String)
Simple case class
case class User(name: String, password: String, age: Option[Int])
Then i get all my case accessors
def getMethods[T: TypeTag] = typeOf[T].decls.sorted.collect {
case m: MethodSymbol if m.isCaseAccessor => m
}.toList
val caseAccessors = getMethods[User]
And i need to call get method with every accessor method and parametrize it by accessorMethod return type
For example:
caseAccessors.map{accessorMehtod => get[accessorMehtod.returnType](accessorMehtod.name)}
Is there any way to do it?
As it said in the comments to your question - it's not possible to extract type from runtime-reflection of object in compile-time - so you're loosing all typechecks (and possible have to do asInstanceOf somewhere).
But, depending on your needs - you could choose some typesafe alternative, like using Shapeless Records, which could also allow you to access fields by name, but in typesafe way. So, there will be just HList[String :: String :: Option[Int]] (which stores all field types inside) instead of List[Method].
You can also easely convert record into the case class: User(userRecord("name"), userRecord("passsword"),userRecord("age"). But it's all requires that possible field names ("name", "password", etc.) should be known at compile time.