I have the following code:
object Order extends Enumeration("asc", "desc") {
type OrderType = Value
val asc, desc = Value
}
And i use it:
val someStr:String = "someStr"
val order = Order.withName(someStr)
This gives me the enum of the input string, but if i send string "asc1" i get Exception:
NoSuchElementException: None.get (ProductRequest.scala
My question is - Can i iterate the values and check if the strings exists? This way i can throw better detailed exception..
I was thinking i can iterate Order.values -> but could not find something useful
Thanks
Your could define your Enumeration as:
object Order extends Enumeration {
type OrderType = Value
val asc = Value("asc")
val desc = Value("desc")
def isOrderType(s: String) = values.exists(_.toString == s)
}
And use it:
Order.isOrderType("asc") //> res0: Boolean = true
Order.isOrderType("foo") //> res1: Boolean = false
This seems to do the trick:
object EnumerationTypes extends App {
object Order extends Enumeration {
type OrderType = Value
val asc = Value("asc")
val desc = Value("desc")
def valueOf(name: String) = this.values.find(_.toString == name)
}
println(Order.valueOf("asc")) // Some(asc)
println(Order.valueOf("ascending")) // None
}
It returns None when the string is not valid instead of throwing an exception.
Related
I have a basic enum type Currency that will include all major currencies traded e.g. EUR, USD, JPY, etc. This code I can write or generate one time. However, I'd also like to have strong enum type for all currency pair combinations e.g. EURCHF, USDCHF, etc. Is there any provision in Scala that would allow me to build such a derived enum type dynamically? I could also do it with some script generator from outside ... but I wonder whether it would be possible.
object Ccy extends Enumeration {
type Type = Value
val USD = Value("USD")
val CHF = Value("CHF")
val EUR = Value("EUR")
val GBP = Value("GBP")
val JPY = Value("JPY")
}
object CcyPair extends Enumeration {
type Type = Value
// ??? Ccy.values.toSeq.combinations(2) ...
}
UPDATE using the accepted answer as reference this was my solution implementation:
import scala.language.dynamics
object CcyPair extends Enumeration with Dynamic {
type Type = Value
/*
* contains all currency combinations including the symmetric AB and BA
*/
private val byCcy: Map[(Ccy.Value, Ccy.Value), Value] =
Ccy.values.toSeq.combinations(2).map { case Seq(c1, c2) =>
Seq(
(c1, c2) -> Value(c1.toString + c2.toString),
(c2, c1) -> Value(c2.toString + c1.toString)
)
}.flatten.toMap
/**
* reverse lookup to find currencies by currency pair, needed to find
* the base and risk components.
*/
private val revByCcy = byCcy.toSeq.map { case (((ccyRisk, ccyBase), ccyPair)) =>
ccyPair -> (ccyRisk, ccyBase)
}.toMap
def apply(ccy1: Ccy.Value, ccy2: Ccy.Value): Value = {
assert(ccy1 != ccy2, "currencies should be different")
byCcy((ccy1, ccy2))
}
implicit class DecoratedCcyPair(ccyPair: CcyPair.Type) {
def base: Ccy.Type = {
revByCcy(ccyPair)._1
}
def risk: Ccy.Type = {
revByCcy(ccyPair)._2
}
def name: String = ccyPair.toString()
}
def selectDynamic(ccyPair: String): Value = withName(ccyPair)
}
and then I can do things like:
val ccyPair = CcyPair.EURUSD
// or
val ccyPair = CcyPair(Ccy.EUR, Ccy.USD)
// and then do
println(ccyPair.name)
// and extract their parts like:
// print the base currency of the pair i.e. EUR
println(CcyPair.EURUSD.base)
// print the risk currency of the pair i.e. USD
println(CcyPair.EURUSD.risk)
There is no magic in Scala's Enumeration. The call to the Value function inside simply does some modifications to Enumeration's internal mutable structures. So you just have to call Value for each pair of currencies. The following code will work:
object CcyPair1 extends Enumeration {
Ccy.values.toSeq.combinations(2).foreach {
case Seq(c1, c2) =>
Value(c1.toString + c2.toString)
}
}
It's not very comfortable to work with though. You can access the values only through withName or values functions.
scala> CcyPair1.withName("USDEUR")
res20: CcyPair1.Value = USDEUR
But it's possible to extend this definition, for example, to allow retrieving CcyPair.Value by a pair of Ccy.Values, or to allow access by object fields with Dynamic, or to provide other facilities you may need:
import scala.language.dynamics
object CcyPair2 extends Enumeration with Dynamic {
val byCcy: Map[(Ccy.Value, Ccy.Value), Value] =
Ccy.values.toSeq.combinations(2).map {
case Seq(c1, c2) =>
(c1, c2) -> Value(c1.toString + c2.toString)
}.toMap
def forCcy(ccy1: Ccy.Value, ccy2: Ccy.Value): Value = {
assert(ccy1 != ccy2, "currencies should be different")
if (ccy1 < ccy2) byCcy((ccy1, ccy2))
else byCcy((ccy2, ccy1))
}
def selectDynamic(pairName: String): Value =
withName(pairName)
}
This definition is a bit more useful:
scala> CcyPair2.forCcy(Ccy.USD, Ccy.EUR)
res2: CcyPair2.Value = USDEUR
scala> CcyPair2.forCcy(Ccy.EUR, Ccy.USD)
res3: CcyPair2.Value = USDEUR
scala> CcyPair2.USDCHF
res4: CcyPair2.Value = USDCHF
I have a use-case where I need to define a new enum type LongShort but I need it in a way to also carry the sign so it can be directly used in mathematical expressions e.g.
object LongShortType extends Enumeration {
type Type = Value
val Long = Value(+1)
val Short = Value(-1)
}
I'd then like to use it like this:
val longShort = LongShortType.Short
val numberOfContracts: Int = 10
val vanillaOptionNotional: Double = longShort*numberOfContracts
but this leads to compiler error cannot resolve symbol * ... is there a way to extract the value of the enum? Or am I not understanding how enum types work?
The type of LongShortType.Short isn't Int, it's Value. You can either extract the underlying id of the value:
val longShort = LongShortType.Short.id
Which is a little ugly. Or you could not use an enum type at all:
object LongShortType {
val Long = 1
val Short = -1
}
And then your equation would work as is.
OK I worked out a solution to accomplish what I wanted without any compromisse and by that I mean that this solution has all the advantages of using Scala enum e.g. the withName and still allows me to define extra features on it:
object LongShortType extends Enumeration {
type Type = LongShortVal
val Long = Value("Long", +1)
val Short = Value("Short", -1)
case class LongShortVal(name: String, sign: Int) extends Val(nextId, name)
protected final def Value(name: String, sign: Int) = new LongShortVal(name, sign)
}
and now can do:
val longShort = LongShortType.Short
val numberOfContracts: Int = 10
val vanillaOptionNotional: Double = longShort.sign*numberOfContracts
and can also do:
val longShort = LongShort.withName("Long") // returns LongShort.Long
In Slick 2.1, I need to perform a query/map operation where I convert a nullable field to None if it contains a certain non-null value. Not sure whether it matters or not, but in my case the column type in question is a mapped column type. Here is a code snippet which tries to illustrate what I'm trying to do. It won't compile, as the compiler doesn't like the None.
case class Record(field1: Int, field2: Int, field3: MyEnum)
sealed trait MyEnum
val MyValue: MyEnum = new MyEnum { }
// table is a TableQuery[Record]
table.map { r => (
r.field1,
r.field2,
Case If (r.field3 === MyValue) Then MyValue Else None // compile error on 'None'
)
}
The error is something like this:
type mismatch; found : None.type required: scala.slick.lifted.Column[MyEnum]
Actually, the reason I want to do this is that I want to perform a groupBy in which I count the number of records whose field3 contains a given value. I couldn't get the more complicated groupBy expression working, so I backed off to this simpler example which I still can't get working. If there's a more direct way to show me the groupBy expression, that would be fine too. Thanks!
Update
I tried the code suggested by #cvogt but this produces a compile error. Here is a SSCCE in case anyone can spot what I'm doing wrong here. Compile fails with "value ? is not a member of Int":
import scala.slick.jdbc.JdbcBackend.Database
import scala.slick.driver.H2Driver
object ExpMain extends App {
val dbName = "mydb"
val db = Database.forURL(s"jdbc:h2:mem:${dbName};DB_CLOSE_DELAY=-1", driver = "org.h2.Driver")
val driver = H2Driver
import driver.simple._
class Exp(tag: Tag) extends Table[(Int, Option[Int])](tag, "EXP") {
def id = column[Int]("ID", O.PrimaryKey)
def value = column[Option[Int]]("VALUE")
def * = (id, value)
}
val exp = TableQuery[Exp]
db withSession { implicit session =>
exp.ddl.create
exp += (1, (Some(1)))
exp += (2, None)
exp += (3, (Some(4)))
exp.map { record =>
Case If (record.value === 1) Then 1.? Else None // this will NOT compile
//Case If (record.value === 1) Then Some(1) Else None // this will NOT compile
//Case If (record.value === 1) Then 1 Else 0 // this will compile
}.foreach {
println
}
}
}
I need to perform a query/map operation where I convert a nullable field to None if it contains a certain non-null value
Given the example data you have in the update, and pretending that 1 is the "certain" value you care about, I believe this is the output you expect:
None, None, Some(4)
(for rows with IDs 1, 2 and 3)
If I've understood the problem correctly, is this what you need...?
val q: Query[Column[Option[Int]], Option[Int], Seq] = exp.map { record =>
Case If (record.value === 1) Then (None: Option[Int]) Else (record.value)
}
...which equates to:
select (case when ("VALUE" = 1) then null else "VALUE" end) from "EXP"
You need to wrap MyValue in an Option, so that both outcomes of the conditional are options. In Slick 2.1 you use the .? operator for that. In Slick 3.0 it will likely be Rep.Some(...).
Try
Case If (r.field3 === MyValue) Then MyValue.? Else None
or
Case If (r.field3 === MyValue) Then MyValue.? Else (None: Option[MyEnum])
I would like to add two new operations to a Scala Enumeration to get the previous and the next value given a certain value if it exists. For example, I would like to write something like:
object Nums extends MyNewEnumerationType {
type Nums = Value
val One,Two,Three = Value
}
Nums.nextOf(One) // Some(Two)
Nums.prevOf(One) // None
My idea was to create a new class and add the methods in this way:
class PrevNextEnum extends Enumeration {
val prevOf = values.zip(None +: values.map{_.some}.toSeq).toMap
val nextOf = {
if (values.isEmpty) Map.empty
else values.zip(values.tail.map{_.some}.toSeq :+ None).toMap
}
}
The problem is that this doesn't work because when prevOf and nextOf are initialized, values is empty.
First question: why values is empty and when it is filled with the values?
Second question: how can I implement prevOf and nextOf?
Third question: is it possible to add the methods prevOf and nextOf to the value type instead of the enumeration? Writing One.next feels more natural than writing Num.nextOf(One)
try the following codes:
class PrevNextEnum extends Enumeration {
lazy val prevOf = {
val list = values.toList
val map = list.tail.zip(list.map(Some(_))).toMap + (list.head -> None)
map
}
lazy val nextOf = {
val list = values.toList
val map = (list.zip(list.tail.map(Some(_)) :+ None).toMap)
map
}
}
object Nums extends PrevNextEnum {
type Nums = Value
val One, Two, Three = Value
}
object App extends App {
println(Nums.prevOf(Nums.Two))
println(Nums.nextOf(Nums.One))
println(Nums.nextOf(Nums.Three))
println(Nums.prevOf(Nums.One))
}
Building on the answer of user1484819 :
class PrevNextEnum extends Enumeration {
lazy val prevOf = {
val list = values.toList
val map = list.tail.zip(list).toMap
v:Value => map.get(v)
}
lazy val nextOf = {
val list = values.toList
val map = list.zip(list.tail).toMap
v:Value => map.get(v)
}
}
object Nums extends PrevNextEnum {
type Nums = Value
val One, Two, Three = Value
}
This has basically the same structure, but uses the fact that Map can return Options itself when using get instead of apply.
I'm currently learning Scala, and just discovered the way to create custom field getters/setters. I have a simple example working:
class Thing(private val a:Int){
override def toString = "Thing[" + a + "]"
private var _value = a
def value = _value
def value_= (newVal:Int) = _value = newVal
}
On the console I can do:
scala> var t = new Thing(2)
t: dylan.code.Thing = Thing[2]
scala> t.value
res1: Int = 2
scala> t.value = 3
scala> t.value
res2: Int = 3
Now I'm trying to bring this concept to a slightly more complicated example; I'll try to whittle the code down to what's relevant:
abstract class CellExpression[Type] extends Publisher[CellUpdateEvent[Type]] with Subscriber[CellUpdateEvent[Type], CellExpression[Type]]{
protected var cachedValue: Type = recalculateValue()
protected def recalculateValue(): Type
protected def changeValue(newValue: Type):Unit = {
val oldValue = value()
if(newValue != oldValue){
cachedValue = newValue
publish(new CellUpdateEvent(this, oldValue, newValue))
}
}
def value() = cachedValue
def notify(pub: CellExpression[Type], event: CellUpdateEvent[Type]) = changeValue(recalculateValue())
}
//....
class CellVariable[Type](private val initialValue:Type) extends CellExpression[Type]{
cachedValue = initialValue
protected def recalculateValue() = { cachedValue }
override def toString = "CellVariable[" + value + "]"
def value_= (newValue:Type) = {changeValue(newValue)}
}
As far as I can tell, I've done what I need to in order to be able to treate value as a field via its getter and setter. But when I try it out in the console, I get:
scala> var i = new CellVariable(2)
i: dylan.code.CellVariable[Int] = CellVariable[2]
scala> i.value = 3
<console>:11: error: reassignment to val
i.value = 3
^
What have I done wrong, and how can I fix it?
I actually stumbled onto the solution.
The line where I declare my value function: def value() = cachedValue is the culprit.
If I remove the parentheses to make the line def value = cachedValue everything seems to work as I expected.
You cannot change values in Scala. A value is assigned once and only once. If you want to do this then you need to use variables instead of values. In other words, change the declaration from val to var.
The problem is inside one of your class definitions and may be on a line without val because I believe that if you neglect to declare a name, then Scala assumes that it is a value and therefore immutable.
Not sure what you want getters and setters for though. Scala enables you to ignore all of that Java overhead.
It is probably the line that says cachedValue = initialValue because it is not declared with var anywhere in that class. The definition in the other class is a different name because it is in a different scope. You would have to say something like class.varname to change a variable defined in another class.