So, I have to parallel class structures, Books and Makers. A Maker creates a book based on some file or something. We can simplify the definition of the basic Maker down to:
class Maker {
type PF = (String, Book) => Book
def apply(in: Source): Book = {
mkMap(in, parseTop)
}
def mkMap(in: Source, top: PF) {
var res = new Book
in.getLines.foreach { ln => res = top(ln, res)
}
def parseTop(line: String, book: Book): Book = {
// really makes a new Book object with changes based on the content of `line` and returns it
book
}
}
and Book looks something like this (not a case class because we want to be able to inherit it)
class Book(val title: String = "Untitled", val author: String = "No Author", val language: String = "English")
def copy(title: String = this.title, author: String = this.author, language: String = this.language) = new Book(title, author, language)
Now, I'd like to extend this to make a SpecialMaker that makes SpecialBooks. A Special Book can be defined like this:
class SpecialBook(title: String = "Untitled", author: String = "No Author", language: String = "English", val specialness: Int = 9000) extends Book(title, author, language)
def copy(title: String = this.title, author: String = this.author, language: String = this.language, specialness: Int = this.specialness) = new SpecialBook(title, author, language, specialness)
The only thing that changes in making a SpecialBook from a Book is that the parseTop function does some additional calculation to make the specialness factor, let's say like this:
class SpecialMaker {
override def parseTop(line: String, book: SpecialBook): SpecialBook = {
book.copy(specialness = book.specialness + 9000)
}
}
Obviously, that code doesn't work because that parseTop would end up assigning a SpecialBook to a Book, among other problems. What's the best way to deal with the issue in Scala? Implicit conversions? (Could something to do with type parameters work?)
If there is a design pattern that deals with this, please let me know what it's called.
My approach, so you define the return type of parseTop by parameterizing the type of Maker:
trait Maker[B <: Book] {
def parseTop(line: String, book: B): B
}
trait Book {
val title: String
val author: String
val language: String
}
case class NonSpecialBook(title: String = "Untitled", author: String = "No Author", language: String = "English") extends Book
case class SpecialBook(title: String = "Untitled", author: String = "No Author", language: String = "English", val specialness: Int = 9000) extends Book
class NonSpecialMaker extends Maker[NonSpecialBook] {
def parseTop(line: String, book: NonSpecialBook): NonSpecialBook = {
book
}
}
class SpecialMaker extends Maker[SpecialBook] {
def parseTop(line: String, book: SpecialBook): SpecialBook = {
book.copy(specialness = book.specialness + 9000)
}
}
Do you need the parseTop method in SpecialMaker to hanle SpecialBooks´s or is it supposed to make books special? Is it possible to have a trait for books and all book types inherit from that trait?
Tell me, what´s missing to fit your needs.
EDIT: (due to comment)
You can supply an implicit BookBuilder :
trait Maker[B <: Book] {
type PF = (String, B) => B
def parseTop(line: String, book: B): B
def mkMap(in: Source, top: PF)(implicit bookBuilder: {def apply(): B} ) {
var res = bookBuilder()
// ...
}
}
But that implicit BookBuilder must be implicitly in scope (or conveyed manually) everytime you use mkMap.
I don´t know when you use mkMap.
Related
Currently I have two classes, how to transfer them to Algebraic Data Types? I think I can do something like this case class BlacklistDynamoDBUpdate(ruleName: String, whitelistedAccount, featureName: String), but how to use those method in that class?
class DynamoDBUpdateBlacklist {
private var features: Array[BlacklistDynamoDBUpdate] = _
def getFeatures = features
def setFeatures(features: Array[BlacklistDynamoDBUpdate]) = {
this.features = features
}
}
class BlacklistDynamoDBUpdate {
private var ruleName: String = _
private var whitelistedAccount: String = _
private var featureName: String = _
def getFeatureName: String = featureName
def setFeatureName(featureName: String) = {
this.featureName = featureName
}
def getRuleName: String = ruleName
def setRuleName(ruleName: String) = {
this.ruleName = ruleName
}
def getWhitelistedAccounts: String = whitelistedAccount
def setWhitelistedAccounts(whitelistedAccount: String): Unit = {
this.whitelistedAccount = whitelistedAccount
}
}
I transfer a json into scala object, json look like this
"features": [ { "featureName": "***", "ruleName": "***", "whitelistedAccounts": "***" }],
what I want is get those attributes value
If you need to parse json into idiomatic scala code then use simple case classes and library that is design to parse such jsons directly into those case classes (personally I sugest this one https://lihaoyi.com/upickle or this one https://circe.github.io/circe).
here is example code that shows how to use upickle.
import upickle.default.{ReadWriter => RW, macroRW}
final case class DynamoDBUpdateBlacklist(features:Seq[BlacklistDynamoDBUpdate])
final case class BlacklistDynamoDBUpdate(featureName:String, ruleName:String, whitelistedAccounts:String)
object DynamoDBUpdateBlacklist {
implicit val rw: RW[DynamoDBUpdateBlacklist] = macroRW
}
object BlacklistDynamoDBUpdate {
implicit val rw: RW[BlacklistDynamoDBUpdate] = macroRW
}
//use it like that
import upickle.default._
println(
read[DynamoDBUpdateBlacklist]("""
{"features":[{ "featureName": "***", "ruleName": "***", "whitelistedAccounts": "***" }]}
""")
)
//DynamoDBUpdateBlacklist(Vector(BlacklistDynamoDBUpdate(***,***,***)))
https://scalafiddle.io/sf/8eqFEfX/2
I have some duplication of code due to having to do some grouping on 3 different fields in a case class and then populate a new case class with those. Since they share a common schema it should be possible for me to do a function that can take the input of the 3 different fields and populate accordingly. However, I am not exactly sure how to do this.
Schemas:
case class Transaction(
senderBank: Bank,
receiverBank: Bank,
intermediaryBank: Bank)
case class Bank(
name: String,
country: Option[String],
countryCode: Option[String])
case class GroupedBank(
name: String,
country: Option[String],
countryCode: Option[String],
bankType: String)
Current function I tried to do:
def groupedBank(transactionSeq: Seq[Transaction], bankName: Bank, bankTypeString: String): Iterable[Seq[GroupedBank]] = {
transactionSeq.groupBy(_ => bankName.name).map {
case (key, transactionSeq) =>
val bankGroupedSeq = transactionSeq.map(_ => {
GroupedBank(
name = bankName.name,
country = bankName.country,
countryCode = bankName.countryCode,
bankType = bankTypeString)
})
bankGroupedSeq
}
}
I need to do the grouping for SenderBank, receiverBank, and intermediaryBank. However, I am not sure how to refer to them correctly in the function parameter bankName. So for SenderBank I would want to do something like Transaction.senderBank, which would point to the correct fields for name, country and so on for senderBank. For receiverBank it should be similar, so Transactions.receiverBank, which then refers to the correct fields for receiverBank and so on. And again for intermediaryBank the same logic. My question is therefore how can I accomplish something like this or is there another way that would be more appropriate?
You can pass a function to extract the bank with the correct type from a transaction:
def groupedBank(
transactionSeq: Seq[Transaction],
getBank: Transaction => Bank,
bankTypeString: String
): Iterable[Seq[GroupedBank]] = {
transactionSeq.groupBy(getBank(_).name).map {
case (key, transactionSeq) =>
transactionSeq.map { transaction =>
val bank = getBank(transaction)
GroupedBank(
name = bank.name,
country = bank.country,
countryCode = bank.countryCode,
bankType = bankTypeString)
}
}
}
And then call it like this:
groupedBank(transactionSeq, _.senderBank, "sender")
It could also be a good idea to abstract the bank type concept into a separate trait:
sealed trait BankGroup {
def name: String
def getBank(transaction: Transaction): Bank
def groupBanks(transactionSeq: Seq[Transaction]): Iterable[Seq[GroupedBank]] = {
transactionSeq.groupBy(getBank(_).name).map {
case (key, transactionSeq) =>
transactionSeq.map { transaction =>
val bank = getBank(transaction)
GroupedBank(
name = bank.name,
country = bank.country,
countryCode = bank.countryCode,
bankType = name)
}
}
}
}
object BankGroup {
object Sender extends BankGroup {
def name: String = "sender"
def getBank(transaction: Transaction): Bank = transaction.senderBank
}
object Receiver extends BankGroup {
def name: String = "receiver"
def getBank(transaction: Transaction): Bank = transaction.receiverBank
}
object Intermediary extends BankGroup {
def name: String = "intermediary"
def getBank(transaction: Transaction): Bank = transaction.intermediaryBank
}
val values: Seq[BankGroup] = Seq(Sender, Receiver, Intermediary)
def byName(name: String): BankGroup = values.find(_.name == name)
.getOrElse(sys.error(s"unknown bank type: $name"))
}
And you can call it in one of those ways:
BankGroup.Sender.groupBanks(transactionSeq)
BankGroup.byName("sender").groupBanks(transactionSeq)
My use case has case classes something like
case class Address(name:String,pincode:String){
override def toString =name +"=" +pincode
}
case class Department(name:String){
override def toString =name
}
case class emp(address:Address,department:Department)
I want to create a DSL like below.Can anyone share the links about how to create a DSL and any suggestions to achieve the below.
emp.withAddress("abc","12222").withDepartment("HR")
Update:
Actual use case class may have more fields close to 20. I want to avoid redudancy of code
I created a DSL using reflection so that we don't need to add every field to it.
Disclamer: This DSL is extremely weakly typed and I did it just for fun. I don't really think this is a good approach in Scala.
scala> create an Employee where "homeAddress" is Address("a", "b") and "department" is Department("c") and that_s it
res0: Employee = Employee(a=b,null,c)
scala> create an Employee where "workAddress" is Address("w", "x") and "homeAddress" is Address("y", "z") and that_s it
res1: Employee = Employee(y=z,w=x,null)
scala> create a Customer where "address" is Address("a", "b") and "age" is 900 and that_s it
res0: Customer = Customer(a=b,900)
The last example is the equivalent of writing:
create.a(Customer).where("address").is(Address("a", "b")).and("age").is(900).and(that_s).it
A way of writing DSLs in Scala and avoid parentheses and the dot is by following this pattern:
object.method(parameter).method(parameter)...
Here is the source:
// DSL
object create {
def an(t: Employee.type) = new ModelDSL(Employee(null, null, null))
def a(t: Customer.type) = new ModelDSL(Customer(null, 0))
}
object that_s
class ModelDSL[T](model: T) {
def where(field: String): ValueDSL[ModelDSL2[T], Any] = new ValueDSL(value => {
val f = model.getClass.getDeclaredField(field)
f.setAccessible(true)
f.set(model, value)
new ModelDSL2[T](model)
})
def and(t: that_s.type) = new { def it = model }
}
class ModelDSL2[T](model: T) {
def and(field: String) = new ModelDSL(model).where(field)
def and(t: that_s.type) = new { def it = model }
}
class ValueDSL[T, V](callback: V => T) {
def is(value: V): T = callback(value)
}
// Models
case class Employee(homeAddress: Address, workAddress: Address, department: Department)
case class Customer(address: Address, age: Int)
case class Address(name: String, pincode: String) {
override def toString = name + "=" + pincode
}
case class Department(name: String) {
override def toString = name
}
I really don't think you need the builder pattern in Scala. Just give your case class reasonable defaults and use the copy method.
i.e.:
employee.copy(address = Address("abc","12222"),
department = Department("HR"))
You could also use an immutable builder:
case class EmployeeBuilder(address:Address = Address("", ""),department:Department = Department("")) {
def build = emp(address, department)
def withAddress(address: Address) = copy(address = address)
def withDepartment(department: Department) = copy(department = department)
}
object EmployeeBuilder {
def withAddress(address: Address) = EmployeeBuilder().copy(address = address)
def withDepartment(department: Department) = EmployeeBuilder().copy(department = department)
}
You could do
object emp {
def builder = new Builder(None, None)
case class Builder(address: Option[Address], department: Option[Department]) {
def withDepartment(name:String) = {
val dept = Department(name)
this.copy(department = Some(dept))
}
def withAddress(name:String, pincode:String) = {
val addr = Address(name, pincode)
this.copy(address = Some(addr))
}
def build = (address, department) match {
case (Some(a), Some(d)) => new emp(a, d)
case (None, _) => throw new IllegalStateException("Address not provided")
case _ => throw new IllegalStateException("Department not provided")
}
}
}
and use it as emp.builder.withAddress("abc","12222").withDepartment("HR").build().
You don't need optional fields, copy, or the builder pattern (exactly), if you are willing to have the build always take the arguments in a particular order:
case class emp(address:Address,department:Department, id: Long)
object emp {
def withAddress(name: String, pincode: String): WithDepartment =
new WithDepartment(Address(name, pincode))
final class WithDepartment(private val address: Address)
extends AnyVal {
def withDepartment(name: String): WithId =
new WithId(address, Department(name))
}
final class WithId(address: Address, department: Department) {
def withId(id: Long): emp = emp(address, department, id)
}
}
emp.withAddress("abc","12222").withDepartment("HR").withId(1)
The idea here is that each emp parameter gets its own class which provides a method to get you to the next class, until the final one gives you an emp object. It's like currying but at the type level. As you can see I've added an extra parameter just as an example of how to extend the pattern past the first two parameters.
The nice thing about this approach is that, even if you're part-way through the build, the type you have so far will guide you to the next step. So if you have a WithDepartment so far, you know that the next argument you need to supply is a department name.
If you want to avoid modifying the origin classes you can use implicit class, e.g.
implicit class EmpExtensions(emp: emp) {
def withAddress(name: String, pincode: String) {
//code omitted
}
// code omitted
}
then import EmpExtensions wherever you need these methods
I tried the example mentioned in Luanne's article The essence of Spring Data Neo4j 4 in Scala. The code can be found in the neo4j-ogm-scala repository.
package neo4j.ogm.scala.domain
import org.neo4j.ogm.annotation.GraphId;
import scala.beans.BeanProperty
import org.neo4j.ogm.annotation.NodeEntity
import org.neo4j.ogm.annotation.Relationship
import org.neo4j.ogm.session.Session;
import org.neo4j.ogm.session.SessionFactory;
abstract class Entity {
#GraphId
#BeanProperty
var id: Long = _
override def equals(o: Any): Boolean = o match {
case other: Entity => other.id.equals(this.id)
case _ => false
}
override def hashCode: Int = id.hashCode()
}
#NodeEntity
class Category extends Entity {
var name: String = _
def this(name: String) {
this()
this.name = name
}
}
#NodeEntity
class Ingredient extends Entity {
var name: String = _
#Relationship(`type` = "HAS_CATEGORY", direction = "OUTGOING")
var category: Category = _
#Relationship(`type` = "PAIRS_WITH", direction = "UNDIRECTED")
var pairings: Set[Pairing] = Set()
def addPairing(pairing: Pairing): Unit = {
pairing.first.pairings +(pairing)
pairing.second.pairings +(pairing)
}
def this(name: String, category: Category) {
this()
this.name = name
this.category = category
}
}
#RelationshipEntity(`type` = "PAIRS_WITH")
class Pairing extends Entity {
#StartNode
var first: Ingredient = _
#EndNode
var second: Ingredient = _
def this(first: Ingredient, second: Ingredient) {
this()
this.first = first
this.second = second
}
}
object Neo4jSessionFactory {
val sessionFactory = new SessionFactory("neo4j.ogm.scala.domain")
def getNeo4jSession(): Session = {
System.setProperty("username", "neo4j")
System.setProperty("password", "neo4j")
sessionFactory.openSession("http://localhost:7474")
}
}
object Main extends App {
val spices = new Category("Spices")
val turmeric = new Ingredient("Turmeric", spices)
val cumin = new Ingredient("Cumin", spices)
val pairing = new Pairing(turmeric, cumin)
cumin.addPairing(pairing)
val session = Neo4jSessionFactory.getNeo4jSession()
val tx: Transaction = session.beginTransaction()
try {
session.save(spices)
session.save(turmeric)
session.save(cumin)
session.save(pairing)
tx.commit()
} catch {
case e: Exception => // tx.rollback()
} finally {
// tx.commit()
}
}
The problem is that nothing gets saved to Neo4j. Can you please point out the problem in my code?
Thanks,
Manoj.
Scala’s Long is an instance of a Value class. Value classes were explicitly introduced to avoid allocating runtime objects. At the JVM level therefore Scala's Long is equivalent to Java’s primitive long which is why it has the primitive type signature J. It cannot be therefore be null, and should not be used as a graphId. Although Scala mostly will do auto-boxing between its own Long and Java’s Long class, this doesn’t apply to declarations, only to operations on those objects.
The #GraphId isn't being picked up on your entities. I have zero knowledge of Scala but it looks like the scala long isn't liked much by the OGM; var id: java.lang.Long = _ works fine.
I have MyObject and MyTrait:
class MyObject(private val myname: String = "") extends MyTrait {
_name = myname
def foo(myname : String) {
_name = myname
}
}
trait MyTrait {
protected var _name: String = _
def name = _name
}
This works fine as this
val myObject = new MyObject("abc")
println(myObject.name)
myObject.foo("def")
println(myObject.name)
prints
abc
def
as expected.
Problem now is that I want MyTrait._name to be a val instead of a var. But there is no way I can manage to get this to compile. Any hints appreciated.
Regards, Oliver
Here is an answer that uses the very latest cutting-edge naming conventions from Rex Kerr and Martin Odersky!
Read it on the scala-debate list. And you thought they sit around working on "higher kinds" and computing with unboxed ints.
There is a PR for the style changes, but this convention will have to wait a bit.
Doc Martin says: That does look promising. I have to experiment with it a little.
So be careful with this stuff; it's experimental and probably chemically unstable.
class MyObject(override protected val initialName: String = "") extends MyTrait {
private var myName: String = initialName
def name_=(newName: String) {
myName = newName
}
override def name = myName
}
trait MyTrait {
protected val initialName: String = "default"
def name = initialName
}
object Test extends App {
val myObject = new MyObject("abc")
println(myObject.name)
myObject.name = "def"
println(myObject.name)
}
The style guide has a section on brevity but is itself not brief. I'm sure there are answers on SO about "prefer def over val in traits" by Daniel Sobral. And don't forget to consult the one-question FAQ when you encounter init-order problems.