val a = user.type match {
case Member => doSomething(if(user.location.isDefined) user.location.get.name else "")
}
I want to safely access the user.location which is a Option[Location] if it exists, otherwise just use an empty string.
case class Location(id: Int, name: String)
Is this possible?
Simply:
doSomething(user.location.map(_.name).getOrElse(""))
Best way to "deep match" into an Option that is nested inside some other object is through a structural pattern match, finally giving a variable name to the desired value and using it in the right side of =>.
I don't know what your "Member" class looks like, but assume that it has two parameters, since we don't care about the first one we put an underscore there "_" and then refer directly to the Location class in the second param, like this:
val a = user match {
case Member(_, Location(_, Some(name))) => doSomething(name)
}
What about this?
val a = user.type match {
case Member(id, type) if(user.location.isDefined) => doSomething(user.location.get.name)
}
If your Member is a case class with attributes id, type for example.
Related
A use case here is to imagine a config object, that is a collection of key-value pairs.
I would like a to create a type representing this collection of key, value pairs. And I'd also like to have the values be of varied types. Further, I'd like to keep those key, value pairs in a single collection, despite the values being of different types. But I don't want to allow the values to by of any type at all. Finally, I'd like to be able to recover the value for a given key. (Bonus points, I'd like to ensure keys are unique)
Something like:
trait Property
case class SProperty(name: String, value:String) extends Property
case class DProperty(name: String, value:Double) extends Property
case class IProperty(name: String, value:Int) extends Property
case class Properties(props: List[Property]) {
def getValueByName(name: String) = {
props.find(p => p.name == name).map(p => p.value)
}
}
And this works ok, but it creates an ugly API. For example:
val properties = Properties(
List(
Property("some name", "some value"),
Property("another name", "another value")
)
)
If I have a test that checks:
properties.getValueByName("some name").getOrElse(None) shouldBe "some value"
the test will fail with
SProperty("some value") is not equal to "some value"
I considered using a shapeless HList, however that would allow Properties to take on any type. I only want Property types in the collection, and I want the values in those to be restricted to types String | Double | Int.
Is there a better way to define this collection of key, value pairs that allows for a specific set of value types and that can provide me with a .get method?
Is there a better way to define my getValueByName method such that it returns the underlying value and not the wrapped value?
One approach is to write the getter like this:
def getValueByName[T](name: String): Option[Any] = {
Properties.find(p => p.name == name).map {
case Property(_, SProperty(v)) => Some(v)
case Property(_, DProperty(v)) => Some(v)
case Property(_, IProperty(v)) => Some(v)
case _ => None
}
}
But this starts to be a lot of places to modify the code to add a new type allowed for the property values. Returning an Option[Any] seems wrong. And to use the getter, you'd need to know the type of the value ahead of time. That may be good, but it may be a hassle.
I was also unable to find an acceptable approach using typeclasses, but I admit that I'm not deeply experienced with writing typeclasses.
I am reading this example from their docs:
class Email(val username: String, val domainName: String)
object Email {
def fromString(emailString: String): Option[Email] = {
emailString.split('#') match {
case Array(a, b) => Some(new Email(a, b))
case _ => None
}
}
}
println(Email.fromString("scala.center#epfl.ch"))
val scalaCenterEmail = Email.fromString("scala.center#epfl.ch")
scalaCenterEmail match {
case Some(email) => println(
s"""Registered an email
|Username: ${email.username}
|Domain name: ${email.domainName}
""")
case None => println("Error: could not parse email")
}
My questions:
What is Some and Option?
What is a factory method (just some function that creates a new object and returns it?)
What is the point of companion objects? Is it just to contain functions that are available to all instances of class? Are they like class methods in Ruby?
What is Some and Option?
Option is a data structure that represents optionality, as the name suggests. Whenever a computation may not return a value, you can return an Option. Option has two cases (represented as two subclasses): Some or None.
In the example above, the method Email.fromString can fail and not return a value. This is represented with Option. In order to know whether the computation yielded a value or not, you can use match and check whether it was a Some or a None:
Email.fromString("scala.center#epfl.ch") match {
case Some(email) => // do something if it's a Some
case None => // do something it it's a None
}
This is much better than returning null because now whoever calls the method can't possibly forget to check the return value.
For example compare this:
def willReturnNull(s: String): String = null
willReturnNull("foo").length() // NullPointerException!
with this
def willReturnNone(s: String): Option[String] = None
willReturnNone("foo").length() // doesn't compile, because 'length' is not a member of `Option`
Also, note that using match is just a way of working with Option. Further discussion would involve using map, flatMap, getOrElse or similar methods defined on Option, but I feel it would be off-topic here.
What is a factory method (just some function that creates a new object and returns it?)
This is nothing specific to Scala. A "factory method" is usually a static method that constructs the value of some type, possibly hiding the details of the type itself. In this case fromString is a factory method because it allows you create an Email without calling the Email constructor with new Email(...)
What is the point of companion objects? Is it just to contain functions that are available to all instances of class? Are they like class methods in Ruby?
As a first approximation, yes. Scala doesn't have static members of a class. Instead, you can have an object associated with that class where you define everything that is static.
E.g. in Java you would have:
public class Email {
public String username;
public String domain;
public static Optional<Email> fromString(String: s) {
// ...
}
}
Where as in Scala you would define the same class as roughly:
class Email(val username: String, val domain: String)
object Email {
def fromString(s: String): Option[Email] = {
// ...
}
}
I would like to add some examples/information to the third question.
If you use akka in companion object you can put every message that you use in case method (it should proceed and use by actor). Moreover, you can add some val for a name of actors or other constant values.
If you work with JSON you should create a format for it (sometimes custom reads and writes). This format you should put inside companion object. Methods to create instances too.
If you go deeper to Scala you can find case classes. So a possibility to create an object of this class without new is because there is a method apply in "default" companion object.
But in general, it's a place where you can put every "static" method etc.
About Option, it provides you a possibility to avoid some exception and make something when you don't have any values.
Gabriele put an example with email, so I'll add another one.
You have a method that sends email, but you take email from User class. The user can have this field empty, so if we have something like it
val maybeEmail: Option[String] = user.email you can use for example map to send an email
maybeEmail.map(email => sendEmail(email))
So if you use it, during writing methods like above you don't need to think that user specify his email or not :)
I have an enumeration of string coming from the result of a query into a database.
In other words, i'm querying a events from a database, and one of the attribute is event description, which should always belong to a set of well known string. E.g.
"PreferedLabelAdded"
"PreferedLabelChanged"
And so on.
I would like to model the possible string that comes from the database within Scala. I was thinking about using an Enum but I don't see how with what i have seen online.
Ultimately, what i would like to do is to compare the value of the attribute to one of this value to perform some tasks according to the type of event descriptions.
Alghough i could go and simply enter my magic string as such
if (eventDesc == "PreferedLabelAdded")
I find it bad, because one does not get one point of contact to change those string, but instead it get spread all over the code.
Maybe simply using an Object as such
Object EventDesc {
val PrefAdded = "PreferedLabelAdded""
val PrefChanged = "...."
}
If anyone has some specific suggestion with that, that would be great.
Daniel
You can extend the scala.Enumeration class to create your enumeration:
object EventDesc extends Enumeration {
type EventDesc = Value
val PreferedLabelAdded, PreferedLabelChanged, UNKNOWN = Value
}
val eventDesc = EventDesc.withName("PreferedLabelAdded")
if (eventDesc == EventDesc.PreferedLabelChanged) {
Console.println(eventDesc)
}
You can also do pattern matching:
eventDesc match {
case PreferedLabelAdded | PreferedLabelChanged => handleEvent(context)
case UNKNOWN => ignoreEvent(context)
case _ => // noop
}
I have a need to take a list of case classes and convert them to a to a single, comma separated string (with no heading or tailing comma).
case class State(name: String)
def toLine(states: State*): String = {
}
so, toLine(State("one"), State("two"), State("three")) should return one,two,three
here is what I thought of. any better way?
def toLine(states: State*): String = {
states match {
case s if s.isEmpty => throw new Exception
case s => s.tail.foldLeft(s.head.name)(_+","+_)
}
}
is there a way to guaranty there will be at least one value in the list?
You can use mkString:
def toLine(states: State*): String = states.map(_.name).mkString(",")
if you need to ensure at least one element you can do:
def toLine(state: State, states: State*) = (state +: states).map(_.name).mkString(",")
Scala's collections provide a function to do just this: states.map(_.name).mkString(",")
If you want to ensure that toLine is always called with at least one value, you can do this:
def toLine(state: State, states: State*): String = {
states.map(_.name).mkString(",")
}
This will move that check to the type system, rather than doing so at runtime.
Im attempting to match a List to certain actions, based on the inner elements of the list, specifically, their types:
def car1 = new Car[Racing] {}
def car2 = new Car[Pickup] {}
var myCars = List(car1,car2)
myCars match {
case Racing :: Pickup => print("your first car is awesome but not your 2nd!"
}
Obviously, the case above is an attempt at pseudocode describing what I'm trying to do, however, it fails.
What is the idiomatic way to match against the types of values that are present in a collection in scala?
I would create a third class (let's call it CarModel) which both Racing and Pickup extend from and then the following code would work:
def traverseList(list: List[CarModel]): Unit = list match {
case (raceCar: Racing) :: rest => {
print("your first car is awesome but not your 2nd!")
traverseList(rest)
}
case (pickUpCar: PickUp) :: rest => {
doSomethingElse()
traverseList(rest)
}
case Nil =>
}
The above is a recursive example to check each type from the list, but you can use simply the match statement with the list if you wanted to check the first element in the list.
If you want to check the type of your list, as #Naetmul mentioned, you will encounter type erasure. This post explains ways to over come it if you want to verify the type of a list.
Please let me know if you have any questions!
Not sure why your using a list if you only have 2 elements - would suggest using a tuple instead. Anyway, I'd do it like this:
myCars match {
case List((_: Racing), (_: Pickup)) =>
print("your first car is awesome but not your 2nd!"
}
Note you don't use the cars in your print statement, and threfore I haven't labelled them - rather I've left them as _