Scala Generic Trait Factory
In my project I have many events that are very similar. Here's a shortened example:
object Events
final case class UpdatedCount(id: Int, prevValue: Double, newValue: Double)
extends PropertyEvent[Double]
final case class UpdatedName(id: Int, prevValue: String, newValue: String)
extends PropertyEvent[String]
The trait looks like this:
trait PropertyEvent[A]
val id: Int
val prevValue: A
val newValue: A
There is a factory that is used to get the appropriate event at runtime. This gets called by another generic method that uses partial functions to get the preValue
and newValue
:
object PropertyEventFactory
def getEvent[A, B <: PropertyEvent[A]](id: Int, preValue: A, newValue: A, prop: B): PropertyEvent[A]= prop match
case UpdatedCount(_,_,_) => UpdatedCount(id, preValue, newValue)
case UpdatedName(_,_,_) => UpdatedName(id, preValue, newValue)
IntelliJ's intelliSense complains about the preValue
and newValue
, but the compiler is able to figure it out and build successfully.
Here is a basic spec to show how this might get called:
"Passing UpdatedCount to the factory" should "result in UpdatedCount" in
val a = PropertyEventFactory.getEvent(0, 1d,2d, UpdatedCount(0,0,0))
assert(a.id == 0)
assert(a.prevValue == 1)
assert(a.newValue == 2)
Is there a way to achieve this by passing UpdatedCount
as a type instead of an object? Creating a temporary version of UpdatedCount
just to get the actual UpdatedCount
Event has code smell to me. I've tried many ways but end up with other issues. Any ideas?
Edit 1:
Added the getEvent
calling function and some additional supporting code to help demonstrate the pattern of use.
Here is the basic entity that is being updated. Forgive the use of vars in the case class as it makes the examples much simpler.
final case class BoxContent(id: Int, var name: String, var count: Double, var stringProp2: String, var intProp: Int)
The command used to request an update:
object Commands
final case class BoxContentUpdateRequest(requestId: Long, entity: BoxContent, fields: Seq[String])
Here is a persistent actor that receives request to update a BoxContent
in a Box
. The method that calls the factory is in here in the editContentProp
function:
class Box extends PersistentActor
override def persistenceId: String = "example"
val contentMap: BoxContentMap = new BoxContentMap()
val receiveCommand: Receive =
case request: BoxContentUpdateRequest =>
val item = request.entity
request.fields.foreach
case "name" => editContentProp(item.id, item.name, contentMap.getNameProp, contentMap.editNameProp, UpdatedName.apply(0,"",""))
case "count" => editContentProp(item.id, item.count, contentMap.getCountProp, contentMap.editCountProp, UpdatedCount.apply(0,0,0))
case "stringProp2" => /*Similar to above*/
case "intProp" => /*Similar to above*/
/*Many more similar cases*/
val receiveRecover: Receive = case _ => /*reload and persist content info here*/
private def editContentProp[A](key: Int, newValue: A, prevGet: Int => A,
editFunc: (Int, A) => Unit, propEvent: PropertyEvent[A]) =
val prevValue = prevGet(key)
persist(PropertyEventFactory.getEvent(key, prevValue, newValue, propEvent)) evt =>
editFunc(key, newValue)
context.system.eventStream.publish(evt)
Edit2:
The suggestion made in the comments to expose a factory method for each event and then pass the factory method seems to be best approach.
Here is the modified Box
class:
class Box extends PersistentActor
override def persistenceId: String = "example"
val contentMap: BoxContentMap = new BoxContentMap()
val receiveCommand: Receive =
case request: BoxContentUpdateRequest =>
val item = request.entity
request.fields.foreach
case "name" => editContentProp(item.id, item.name, contentMap.getNameProp, contentMap.editNameProp, PropertyEventFactory.getNameEvent)
case "count" => editContentProp(item.id, item.count, contentMap.getCountProp, contentMap.editCountProp, PropertyEventFactory.getCountEvent)
case "stringProp2" => /*Similar to above*/
case "intProp" => /*Similar to above*/
/*Many more similar cases*/
val receiveRecover: Receive = case _ => /*reload and persist content info here*/
private def editContentProp[A](key: Int, newValue: A, prevGet: Int => A,
editFunc: (Int, A) => Unit, eventFactMethod: (Int, A, A) => PropertyEvent[A]) =
val prevValue = prevGet(key)
persist(eventFactMethod(key, prevValue, newValue)) evt =>
editFunc(key, newValue)
context.system.eventStream.publish(evt)
And here is the modified PropertyEventFactory
:
object PropertyEventFactory
def getCountEvent(id: Int, preValue: Double, newValue: Double): UpdatedCount = UpdatedCount(id, preValue, newValue)
def getNameEvent(id: Int, preValue: String, newValue: String): UpdatedName = UpdatedName(id, preValue, newValue)
If one of the commenters who suggested this approach want to propose an answer with this content I'll be happy to upvote it.
scala generics traits
|
show 5 more comments
In my project I have many events that are very similar. Here's a shortened example:
object Events
final case class UpdatedCount(id: Int, prevValue: Double, newValue: Double)
extends PropertyEvent[Double]
final case class UpdatedName(id: Int, prevValue: String, newValue: String)
extends PropertyEvent[String]
The trait looks like this:
trait PropertyEvent[A]
val id: Int
val prevValue: A
val newValue: A
There is a factory that is used to get the appropriate event at runtime. This gets called by another generic method that uses partial functions to get the preValue
and newValue
:
object PropertyEventFactory
def getEvent[A, B <: PropertyEvent[A]](id: Int, preValue: A, newValue: A, prop: B): PropertyEvent[A]= prop match
case UpdatedCount(_,_,_) => UpdatedCount(id, preValue, newValue)
case UpdatedName(_,_,_) => UpdatedName(id, preValue, newValue)
IntelliJ's intelliSense complains about the preValue
and newValue
, but the compiler is able to figure it out and build successfully.
Here is a basic spec to show how this might get called:
"Passing UpdatedCount to the factory" should "result in UpdatedCount" in
val a = PropertyEventFactory.getEvent(0, 1d,2d, UpdatedCount(0,0,0))
assert(a.id == 0)
assert(a.prevValue == 1)
assert(a.newValue == 2)
Is there a way to achieve this by passing UpdatedCount
as a type instead of an object? Creating a temporary version of UpdatedCount
just to get the actual UpdatedCount
Event has code smell to me. I've tried many ways but end up with other issues. Any ideas?
Edit 1:
Added the getEvent
calling function and some additional supporting code to help demonstrate the pattern of use.
Here is the basic entity that is being updated. Forgive the use of vars in the case class as it makes the examples much simpler.
final case class BoxContent(id: Int, var name: String, var count: Double, var stringProp2: String, var intProp: Int)
The command used to request an update:
object Commands
final case class BoxContentUpdateRequest(requestId: Long, entity: BoxContent, fields: Seq[String])
Here is a persistent actor that receives request to update a BoxContent
in a Box
. The method that calls the factory is in here in the editContentProp
function:
class Box extends PersistentActor
override def persistenceId: String = "example"
val contentMap: BoxContentMap = new BoxContentMap()
val receiveCommand: Receive =
case request: BoxContentUpdateRequest =>
val item = request.entity
request.fields.foreach
case "name" => editContentProp(item.id, item.name, contentMap.getNameProp, contentMap.editNameProp, UpdatedName.apply(0,"",""))
case "count" => editContentProp(item.id, item.count, contentMap.getCountProp, contentMap.editCountProp, UpdatedCount.apply(0,0,0))
case "stringProp2" => /*Similar to above*/
case "intProp" => /*Similar to above*/
/*Many more similar cases*/
val receiveRecover: Receive = case _ => /*reload and persist content info here*/
private def editContentProp[A](key: Int, newValue: A, prevGet: Int => A,
editFunc: (Int, A) => Unit, propEvent: PropertyEvent[A]) =
val prevValue = prevGet(key)
persist(PropertyEventFactory.getEvent(key, prevValue, newValue, propEvent)) evt =>
editFunc(key, newValue)
context.system.eventStream.publish(evt)
Edit2:
The suggestion made in the comments to expose a factory method for each event and then pass the factory method seems to be best approach.
Here is the modified Box
class:
class Box extends PersistentActor
override def persistenceId: String = "example"
val contentMap: BoxContentMap = new BoxContentMap()
val receiveCommand: Receive =
case request: BoxContentUpdateRequest =>
val item = request.entity
request.fields.foreach
case "name" => editContentProp(item.id, item.name, contentMap.getNameProp, contentMap.editNameProp, PropertyEventFactory.getNameEvent)
case "count" => editContentProp(item.id, item.count, contentMap.getCountProp, contentMap.editCountProp, PropertyEventFactory.getCountEvent)
case "stringProp2" => /*Similar to above*/
case "intProp" => /*Similar to above*/
/*Many more similar cases*/
val receiveRecover: Receive = case _ => /*reload and persist content info here*/
private def editContentProp[A](key: Int, newValue: A, prevGet: Int => A,
editFunc: (Int, A) => Unit, eventFactMethod: (Int, A, A) => PropertyEvent[A]) =
val prevValue = prevGet(key)
persist(eventFactMethod(key, prevValue, newValue)) evt =>
editFunc(key, newValue)
context.system.eventStream.publish(evt)
And here is the modified PropertyEventFactory
:
object PropertyEventFactory
def getCountEvent(id: Int, preValue: Double, newValue: Double): UpdatedCount = UpdatedCount(id, preValue, newValue)
def getNameEvent(id: Int, preValue: String, newValue: String): UpdatedName = UpdatedName(id, preValue, newValue)
If one of the commenters who suggested this approach want to propose an answer with this content I'll be happy to upvote it.
scala generics traits
If you know the event type when you callgetEvent
, why not have a separate factory method for each event and just called the appropriate method?
– Tim
Nov 16 '18 at 9:10
getEvent
gets called by a another generic method, so I doesn't know the event type. It has 2 function arguments (to get thepreValue
andnewValue
values) and aPropertyEvent[A]
argument that then it passes togetEvent
when it makes its request.
– MORCHARD
Nov 16 '18 at 14:04
I think we need to see the calling function, since that may be where the problem is. But in general you can't use a type parameter to make decisions about how to process data, you need a value for that.
– Tim
Nov 16 '18 at 15:03
Why your example compiles, baffles me. Can anyone explain ?
– ygor
Nov 16 '18 at 15:51
Responding to first comment:def getEvent[A, B <: PropertyEvent[A]](id: Int, preValue: A, newValue: A, fact: (Int, A, A) => B): PropertyEvent[A] = fact(id, preValue, newValue)
and than call it usingPropertyEventFactory3.getEvent(0, 1d, 2d, UpdatedCount.apply)
– ygor
Nov 16 '18 at 15:52
|
show 5 more comments
In my project I have many events that are very similar. Here's a shortened example:
object Events
final case class UpdatedCount(id: Int, prevValue: Double, newValue: Double)
extends PropertyEvent[Double]
final case class UpdatedName(id: Int, prevValue: String, newValue: String)
extends PropertyEvent[String]
The trait looks like this:
trait PropertyEvent[A]
val id: Int
val prevValue: A
val newValue: A
There is a factory that is used to get the appropriate event at runtime. This gets called by another generic method that uses partial functions to get the preValue
and newValue
:
object PropertyEventFactory
def getEvent[A, B <: PropertyEvent[A]](id: Int, preValue: A, newValue: A, prop: B): PropertyEvent[A]= prop match
case UpdatedCount(_,_,_) => UpdatedCount(id, preValue, newValue)
case UpdatedName(_,_,_) => UpdatedName(id, preValue, newValue)
IntelliJ's intelliSense complains about the preValue
and newValue
, but the compiler is able to figure it out and build successfully.
Here is a basic spec to show how this might get called:
"Passing UpdatedCount to the factory" should "result in UpdatedCount" in
val a = PropertyEventFactory.getEvent(0, 1d,2d, UpdatedCount(0,0,0))
assert(a.id == 0)
assert(a.prevValue == 1)
assert(a.newValue == 2)
Is there a way to achieve this by passing UpdatedCount
as a type instead of an object? Creating a temporary version of UpdatedCount
just to get the actual UpdatedCount
Event has code smell to me. I've tried many ways but end up with other issues. Any ideas?
Edit 1:
Added the getEvent
calling function and some additional supporting code to help demonstrate the pattern of use.
Here is the basic entity that is being updated. Forgive the use of vars in the case class as it makes the examples much simpler.
final case class BoxContent(id: Int, var name: String, var count: Double, var stringProp2: String, var intProp: Int)
The command used to request an update:
object Commands
final case class BoxContentUpdateRequest(requestId: Long, entity: BoxContent, fields: Seq[String])
Here is a persistent actor that receives request to update a BoxContent
in a Box
. The method that calls the factory is in here in the editContentProp
function:
class Box extends PersistentActor
override def persistenceId: String = "example"
val contentMap: BoxContentMap = new BoxContentMap()
val receiveCommand: Receive =
case request: BoxContentUpdateRequest =>
val item = request.entity
request.fields.foreach
case "name" => editContentProp(item.id, item.name, contentMap.getNameProp, contentMap.editNameProp, UpdatedName.apply(0,"",""))
case "count" => editContentProp(item.id, item.count, contentMap.getCountProp, contentMap.editCountProp, UpdatedCount.apply(0,0,0))
case "stringProp2" => /*Similar to above*/
case "intProp" => /*Similar to above*/
/*Many more similar cases*/
val receiveRecover: Receive = case _ => /*reload and persist content info here*/
private def editContentProp[A](key: Int, newValue: A, prevGet: Int => A,
editFunc: (Int, A) => Unit, propEvent: PropertyEvent[A]) =
val prevValue = prevGet(key)
persist(PropertyEventFactory.getEvent(key, prevValue, newValue, propEvent)) evt =>
editFunc(key, newValue)
context.system.eventStream.publish(evt)
Edit2:
The suggestion made in the comments to expose a factory method for each event and then pass the factory method seems to be best approach.
Here is the modified Box
class:
class Box extends PersistentActor
override def persistenceId: String = "example"
val contentMap: BoxContentMap = new BoxContentMap()
val receiveCommand: Receive =
case request: BoxContentUpdateRequest =>
val item = request.entity
request.fields.foreach
case "name" => editContentProp(item.id, item.name, contentMap.getNameProp, contentMap.editNameProp, PropertyEventFactory.getNameEvent)
case "count" => editContentProp(item.id, item.count, contentMap.getCountProp, contentMap.editCountProp, PropertyEventFactory.getCountEvent)
case "stringProp2" => /*Similar to above*/
case "intProp" => /*Similar to above*/
/*Many more similar cases*/
val receiveRecover: Receive = case _ => /*reload and persist content info here*/
private def editContentProp[A](key: Int, newValue: A, prevGet: Int => A,
editFunc: (Int, A) => Unit, eventFactMethod: (Int, A, A) => PropertyEvent[A]) =
val prevValue = prevGet(key)
persist(eventFactMethod(key, prevValue, newValue)) evt =>
editFunc(key, newValue)
context.system.eventStream.publish(evt)
And here is the modified PropertyEventFactory
:
object PropertyEventFactory
def getCountEvent(id: Int, preValue: Double, newValue: Double): UpdatedCount = UpdatedCount(id, preValue, newValue)
def getNameEvent(id: Int, preValue: String, newValue: String): UpdatedName = UpdatedName(id, preValue, newValue)
If one of the commenters who suggested this approach want to propose an answer with this content I'll be happy to upvote it.
scala generics traits
In my project I have many events that are very similar. Here's a shortened example:
object Events
final case class UpdatedCount(id: Int, prevValue: Double, newValue: Double)
extends PropertyEvent[Double]
final case class UpdatedName(id: Int, prevValue: String, newValue: String)
extends PropertyEvent[String]
The trait looks like this:
trait PropertyEvent[A]
val id: Int
val prevValue: A
val newValue: A
There is a factory that is used to get the appropriate event at runtime. This gets called by another generic method that uses partial functions to get the preValue
and newValue
:
object PropertyEventFactory
def getEvent[A, B <: PropertyEvent[A]](id: Int, preValue: A, newValue: A, prop: B): PropertyEvent[A]= prop match
case UpdatedCount(_,_,_) => UpdatedCount(id, preValue, newValue)
case UpdatedName(_,_,_) => UpdatedName(id, preValue, newValue)
IntelliJ's intelliSense complains about the preValue
and newValue
, but the compiler is able to figure it out and build successfully.
Here is a basic spec to show how this might get called:
"Passing UpdatedCount to the factory" should "result in UpdatedCount" in
val a = PropertyEventFactory.getEvent(0, 1d,2d, UpdatedCount(0,0,0))
assert(a.id == 0)
assert(a.prevValue == 1)
assert(a.newValue == 2)
Is there a way to achieve this by passing UpdatedCount
as a type instead of an object? Creating a temporary version of UpdatedCount
just to get the actual UpdatedCount
Event has code smell to me. I've tried many ways but end up with other issues. Any ideas?
Edit 1:
Added the getEvent
calling function and some additional supporting code to help demonstrate the pattern of use.
Here is the basic entity that is being updated. Forgive the use of vars in the case class as it makes the examples much simpler.
final case class BoxContent(id: Int, var name: String, var count: Double, var stringProp2: String, var intProp: Int)
The command used to request an update:
object Commands
final case class BoxContentUpdateRequest(requestId: Long, entity: BoxContent, fields: Seq[String])
Here is a persistent actor that receives request to update a BoxContent
in a Box
. The method that calls the factory is in here in the editContentProp
function:
class Box extends PersistentActor
override def persistenceId: String = "example"
val contentMap: BoxContentMap = new BoxContentMap()
val receiveCommand: Receive =
case request: BoxContentUpdateRequest =>
val item = request.entity
request.fields.foreach
case "name" => editContentProp(item.id, item.name, contentMap.getNameProp, contentMap.editNameProp, UpdatedName.apply(0,"",""))
case "count" => editContentProp(item.id, item.count, contentMap.getCountProp, contentMap.editCountProp, UpdatedCount.apply(0,0,0))
case "stringProp2" => /*Similar to above*/
case "intProp" => /*Similar to above*/
/*Many more similar cases*/
val receiveRecover: Receive = case _ => /*reload and persist content info here*/
private def editContentProp[A](key: Int, newValue: A, prevGet: Int => A,
editFunc: (Int, A) => Unit, propEvent: PropertyEvent[A]) =
val prevValue = prevGet(key)
persist(PropertyEventFactory.getEvent(key, prevValue, newValue, propEvent)) evt =>
editFunc(key, newValue)
context.system.eventStream.publish(evt)
Edit2:
The suggestion made in the comments to expose a factory method for each event and then pass the factory method seems to be best approach.
Here is the modified Box
class:
class Box extends PersistentActor
override def persistenceId: String = "example"
val contentMap: BoxContentMap = new BoxContentMap()
val receiveCommand: Receive =
case request: BoxContentUpdateRequest =>
val item = request.entity
request.fields.foreach
case "name" => editContentProp(item.id, item.name, contentMap.getNameProp, contentMap.editNameProp, PropertyEventFactory.getNameEvent)
case "count" => editContentProp(item.id, item.count, contentMap.getCountProp, contentMap.editCountProp, PropertyEventFactory.getCountEvent)
case "stringProp2" => /*Similar to above*/
case "intProp" => /*Similar to above*/
/*Many more similar cases*/
val receiveRecover: Receive = case _ => /*reload and persist content info here*/
private def editContentProp[A](key: Int, newValue: A, prevGet: Int => A,
editFunc: (Int, A) => Unit, eventFactMethod: (Int, A, A) => PropertyEvent[A]) =
val prevValue = prevGet(key)
persist(eventFactMethod(key, prevValue, newValue)) evt =>
editFunc(key, newValue)
context.system.eventStream.publish(evt)
And here is the modified PropertyEventFactory
:
object PropertyEventFactory
def getCountEvent(id: Int, preValue: Double, newValue: Double): UpdatedCount = UpdatedCount(id, preValue, newValue)
def getNameEvent(id: Int, preValue: String, newValue: String): UpdatedName = UpdatedName(id, preValue, newValue)
If one of the commenters who suggested this approach want to propose an answer with this content I'll be happy to upvote it.
scala generics traits
scala generics traits
edited Nov 16 '18 at 19:19
MORCHARD
asked Nov 15 '18 at 20:23
MORCHARDMORCHARD
218211
218211
If you know the event type when you callgetEvent
, why not have a separate factory method for each event and just called the appropriate method?
– Tim
Nov 16 '18 at 9:10
getEvent
gets called by a another generic method, so I doesn't know the event type. It has 2 function arguments (to get thepreValue
andnewValue
values) and aPropertyEvent[A]
argument that then it passes togetEvent
when it makes its request.
– MORCHARD
Nov 16 '18 at 14:04
I think we need to see the calling function, since that may be where the problem is. But in general you can't use a type parameter to make decisions about how to process data, you need a value for that.
– Tim
Nov 16 '18 at 15:03
Why your example compiles, baffles me. Can anyone explain ?
– ygor
Nov 16 '18 at 15:51
Responding to first comment:def getEvent[A, B <: PropertyEvent[A]](id: Int, preValue: A, newValue: A, fact: (Int, A, A) => B): PropertyEvent[A] = fact(id, preValue, newValue)
and than call it usingPropertyEventFactory3.getEvent(0, 1d, 2d, UpdatedCount.apply)
– ygor
Nov 16 '18 at 15:52
|
show 5 more comments
If you know the event type when you callgetEvent
, why not have a separate factory method for each event and just called the appropriate method?
– Tim
Nov 16 '18 at 9:10
getEvent
gets called by a another generic method, so I doesn't know the event type. It has 2 function arguments (to get thepreValue
andnewValue
values) and aPropertyEvent[A]
argument that then it passes togetEvent
when it makes its request.
– MORCHARD
Nov 16 '18 at 14:04
I think we need to see the calling function, since that may be where the problem is. But in general you can't use a type parameter to make decisions about how to process data, you need a value for that.
– Tim
Nov 16 '18 at 15:03
Why your example compiles, baffles me. Can anyone explain ?
– ygor
Nov 16 '18 at 15:51
Responding to first comment:def getEvent[A, B <: PropertyEvent[A]](id: Int, preValue: A, newValue: A, fact: (Int, A, A) => B): PropertyEvent[A] = fact(id, preValue, newValue)
and than call it usingPropertyEventFactory3.getEvent(0, 1d, 2d, UpdatedCount.apply)
– ygor
Nov 16 '18 at 15:52
If you know the event type when you call
getEvent
, why not have a separate factory method for each event and just called the appropriate method?– Tim
Nov 16 '18 at 9:10
If you know the event type when you call
getEvent
, why not have a separate factory method for each event and just called the appropriate method?– Tim
Nov 16 '18 at 9:10
getEvent
gets called by a another generic method, so I doesn't know the event type. It has 2 function arguments (to get the preValue
and newValue
values) and a PropertyEvent[A]
argument that then it passes to getEvent
when it makes its request.– MORCHARD
Nov 16 '18 at 14:04
getEvent
gets called by a another generic method, so I doesn't know the event type. It has 2 function arguments (to get the preValue
and newValue
values) and a PropertyEvent[A]
argument that then it passes to getEvent
when it makes its request.– MORCHARD
Nov 16 '18 at 14:04
I think we need to see the calling function, since that may be where the problem is. But in general you can't use a type parameter to make decisions about how to process data, you need a value for that.
– Tim
Nov 16 '18 at 15:03
I think we need to see the calling function, since that may be where the problem is. But in general you can't use a type parameter to make decisions about how to process data, you need a value for that.
– Tim
Nov 16 '18 at 15:03
Why your example compiles, baffles me. Can anyone explain ?
– ygor
Nov 16 '18 at 15:51
Why your example compiles, baffles me. Can anyone explain ?
– ygor
Nov 16 '18 at 15:51
Responding to first comment:
def getEvent[A, B <: PropertyEvent[A]](id: Int, preValue: A, newValue: A, fact: (Int, A, A) => B): PropertyEvent[A] = fact(id, preValue, newValue)
and than call it using PropertyEventFactory3.getEvent(0, 1d, 2d, UpdatedCount.apply)
– ygor
Nov 16 '18 at 15:52
Responding to first comment:
def getEvent[A, B <: PropertyEvent[A]](id: Int, preValue: A, newValue: A, fact: (Int, A, A) => B): PropertyEvent[A] = fact(id, preValue, newValue)
and than call it using PropertyEventFactory3.getEvent(0, 1d, 2d, UpdatedCount.apply)
– ygor
Nov 16 '18 at 15:52
|
show 5 more comments
1 Answer
1
active
oldest
votes
This is my attempt to summarize an answer.
First of all, there is no such thing as a generic factory for your trait. Your trait PropertyEvent
only specifies three vals
, which every subclass of the trait must fulfill after creation. Every class, which implements the trait, can have very different constructors and/or factories.
So, you really need to "enumerate" those factories manually somewhere. Your first attempt works, but it really suffers from code smell and frankly, I am very surprised, that it even compiles. Scala compiler must somehow be able to narrow down the generic A
type to a concrete type, once inside a match
/case
of a case class.
If you try something like this:
object PropertyEventFactory2
def getEvent[A, B <: PropertyEvent[A]](id: Int, preValue: A, newValue: A, prop: Class[B]): B = prop.getName match
case "org.example.UpdatedCount" => UpdatedCount(id, preValue, newValue)
case "org.example.UpdatedName" => UpdatedName(id, preValue, newValue)
Than this does not compile. You'd need to cast preValue
and newValue
to the appropriate type and this also is a smelly code.
You could create the event before calling editContentProp
:
case "name" =>
val event = UpdatedName(item.id, contentMap.getNameProp(item.id), item.name)
editContentProp(item.id, item.name, contentMap.getNameProp, contentMap.editNameProp, event)
However, all your case
branches would repeat the same structure, which is a kind of code duplication. You already recognized it, which is good.
So your best choice really is to pass in a factory for every event. And because all your events are case classes, for every case class you receive a factory method for free, generated by Scala compiler. The factory method resides in the companion object of the case class and it is simply called CaseClass.apply
This leads to the final form of your case
branch:
case "name" => editContentProp(item.id, item.name, contentMap.getNameProp, contentMap.editNameProp, UpdatedName.apply)
which is consumed by parameter:
eventFactMethod: (Int, A, A)
Usage ofCaseClass.apply
is highlighted in PlayFramework's forms: playframework.com/documentation/2.6.x/ScalaForms
– ygor
Nov 16 '18 at 21:05
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53327361%2fscala-generic-trait-factory%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
This is my attempt to summarize an answer.
First of all, there is no such thing as a generic factory for your trait. Your trait PropertyEvent
only specifies three vals
, which every subclass of the trait must fulfill after creation. Every class, which implements the trait, can have very different constructors and/or factories.
So, you really need to "enumerate" those factories manually somewhere. Your first attempt works, but it really suffers from code smell and frankly, I am very surprised, that it even compiles. Scala compiler must somehow be able to narrow down the generic A
type to a concrete type, once inside a match
/case
of a case class.
If you try something like this:
object PropertyEventFactory2
def getEvent[A, B <: PropertyEvent[A]](id: Int, preValue: A, newValue: A, prop: Class[B]): B = prop.getName match
case "org.example.UpdatedCount" => UpdatedCount(id, preValue, newValue)
case "org.example.UpdatedName" => UpdatedName(id, preValue, newValue)
Than this does not compile. You'd need to cast preValue
and newValue
to the appropriate type and this also is a smelly code.
You could create the event before calling editContentProp
:
case "name" =>
val event = UpdatedName(item.id, contentMap.getNameProp(item.id), item.name)
editContentProp(item.id, item.name, contentMap.getNameProp, contentMap.editNameProp, event)
However, all your case
branches would repeat the same structure, which is a kind of code duplication. You already recognized it, which is good.
So your best choice really is to pass in a factory for every event. And because all your events are case classes, for every case class you receive a factory method for free, generated by Scala compiler. The factory method resides in the companion object of the case class and it is simply called CaseClass.apply
This leads to the final form of your case
branch:
case "name" => editContentProp(item.id, item.name, contentMap.getNameProp, contentMap.editNameProp, UpdatedName.apply)
which is consumed by parameter:
eventFactMethod: (Int, A, A)
Usage ofCaseClass.apply
is highlighted in PlayFramework's forms: playframework.com/documentation/2.6.x/ScalaForms
– ygor
Nov 16 '18 at 21:05
add a comment |
This is my attempt to summarize an answer.
First of all, there is no such thing as a generic factory for your trait. Your trait PropertyEvent
only specifies three vals
, which every subclass of the trait must fulfill after creation. Every class, which implements the trait, can have very different constructors and/or factories.
So, you really need to "enumerate" those factories manually somewhere. Your first attempt works, but it really suffers from code smell and frankly, I am very surprised, that it even compiles. Scala compiler must somehow be able to narrow down the generic A
type to a concrete type, once inside a match
/case
of a case class.
If you try something like this:
object PropertyEventFactory2
def getEvent[A, B <: PropertyEvent[A]](id: Int, preValue: A, newValue: A, prop: Class[B]): B = prop.getName match
case "org.example.UpdatedCount" => UpdatedCount(id, preValue, newValue)
case "org.example.UpdatedName" => UpdatedName(id, preValue, newValue)
Than this does not compile. You'd need to cast preValue
and newValue
to the appropriate type and this also is a smelly code.
You could create the event before calling editContentProp
:
case "name" =>
val event = UpdatedName(item.id, contentMap.getNameProp(item.id), item.name)
editContentProp(item.id, item.name, contentMap.getNameProp, contentMap.editNameProp, event)
However, all your case
branches would repeat the same structure, which is a kind of code duplication. You already recognized it, which is good.
So your best choice really is to pass in a factory for every event. And because all your events are case classes, for every case class you receive a factory method for free, generated by Scala compiler. The factory method resides in the companion object of the case class and it is simply called CaseClass.apply
This leads to the final form of your case
branch:
case "name" => editContentProp(item.id, item.name, contentMap.getNameProp, contentMap.editNameProp, UpdatedName.apply)
which is consumed by parameter:
eventFactMethod: (Int, A, A)
Usage ofCaseClass.apply
is highlighted in PlayFramework's forms: playframework.com/documentation/2.6.x/ScalaForms
– ygor
Nov 16 '18 at 21:05
add a comment |
This is my attempt to summarize an answer.
First of all, there is no such thing as a generic factory for your trait. Your trait PropertyEvent
only specifies three vals
, which every subclass of the trait must fulfill after creation. Every class, which implements the trait, can have very different constructors and/or factories.
So, you really need to "enumerate" those factories manually somewhere. Your first attempt works, but it really suffers from code smell and frankly, I am very surprised, that it even compiles. Scala compiler must somehow be able to narrow down the generic A
type to a concrete type, once inside a match
/case
of a case class.
If you try something like this:
object PropertyEventFactory2
def getEvent[A, B <: PropertyEvent[A]](id: Int, preValue: A, newValue: A, prop: Class[B]): B = prop.getName match
case "org.example.UpdatedCount" => UpdatedCount(id, preValue, newValue)
case "org.example.UpdatedName" => UpdatedName(id, preValue, newValue)
Than this does not compile. You'd need to cast preValue
and newValue
to the appropriate type and this also is a smelly code.
You could create the event before calling editContentProp
:
case "name" =>
val event = UpdatedName(item.id, contentMap.getNameProp(item.id), item.name)
editContentProp(item.id, item.name, contentMap.getNameProp, contentMap.editNameProp, event)
However, all your case
branches would repeat the same structure, which is a kind of code duplication. You already recognized it, which is good.
So your best choice really is to pass in a factory for every event. And because all your events are case classes, for every case class you receive a factory method for free, generated by Scala compiler. The factory method resides in the companion object of the case class and it is simply called CaseClass.apply
This leads to the final form of your case
branch:
case "name" => editContentProp(item.id, item.name, contentMap.getNameProp, contentMap.editNameProp, UpdatedName.apply)
which is consumed by parameter:
eventFactMethod: (Int, A, A)
This is my attempt to summarize an answer.
First of all, there is no such thing as a generic factory for your trait. Your trait PropertyEvent
only specifies three vals
, which every subclass of the trait must fulfill after creation. Every class, which implements the trait, can have very different constructors and/or factories.
So, you really need to "enumerate" those factories manually somewhere. Your first attempt works, but it really suffers from code smell and frankly, I am very surprised, that it even compiles. Scala compiler must somehow be able to narrow down the generic A
type to a concrete type, once inside a match
/case
of a case class.
If you try something like this:
object PropertyEventFactory2
def getEvent[A, B <: PropertyEvent[A]](id: Int, preValue: A, newValue: A, prop: Class[B]): B = prop.getName match
case "org.example.UpdatedCount" => UpdatedCount(id, preValue, newValue)
case "org.example.UpdatedName" => UpdatedName(id, preValue, newValue)
Than this does not compile. You'd need to cast preValue
and newValue
to the appropriate type and this also is a smelly code.
You could create the event before calling editContentProp
:
case "name" =>
val event = UpdatedName(item.id, contentMap.getNameProp(item.id), item.name)
editContentProp(item.id, item.name, contentMap.getNameProp, contentMap.editNameProp, event)
However, all your case
branches would repeat the same structure, which is a kind of code duplication. You already recognized it, which is good.
So your best choice really is to pass in a factory for every event. And because all your events are case classes, for every case class you receive a factory method for free, generated by Scala compiler. The factory method resides in the companion object of the case class and it is simply called CaseClass.apply
This leads to the final form of your case
branch:
case "name" => editContentProp(item.id, item.name, contentMap.getNameProp, contentMap.editNameProp, UpdatedName.apply)
which is consumed by parameter:
eventFactMethod: (Int, A, A)
answered Nov 16 '18 at 21:03
ygorygor
1,1161615
1,1161615
Usage ofCaseClass.apply
is highlighted in PlayFramework's forms: playframework.com/documentation/2.6.x/ScalaForms
– ygor
Nov 16 '18 at 21:05
add a comment |
Usage ofCaseClass.apply
is highlighted in PlayFramework's forms: playframework.com/documentation/2.6.x/ScalaForms
– ygor
Nov 16 '18 at 21:05
Usage of
CaseClass.apply
is highlighted in PlayFramework's forms: playframework.com/documentation/2.6.x/ScalaForms– ygor
Nov 16 '18 at 21:05
Usage of
CaseClass.apply
is highlighted in PlayFramework's forms: playframework.com/documentation/2.6.x/ScalaForms– ygor
Nov 16 '18 at 21:05
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53327361%2fscala-generic-trait-factory%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
If you know the event type when you call
getEvent
, why not have a separate factory method for each event and just called the appropriate method?– Tim
Nov 16 '18 at 9:10
getEvent
gets called by a another generic method, so I doesn't know the event type. It has 2 function arguments (to get thepreValue
andnewValue
values) and aPropertyEvent[A]
argument that then it passes togetEvent
when it makes its request.– MORCHARD
Nov 16 '18 at 14:04
I think we need to see the calling function, since that may be where the problem is. But in general you can't use a type parameter to make decisions about how to process data, you need a value for that.
– Tim
Nov 16 '18 at 15:03
Why your example compiles, baffles me. Can anyone explain ?
– ygor
Nov 16 '18 at 15:51
Responding to first comment:
def getEvent[A, B <: PropertyEvent[A]](id: Int, preValue: A, newValue: A, fact: (Int, A, A) => B): PropertyEvent[A] = fact(id, preValue, newValue)
and than call it usingPropertyEventFactory3.getEvent(0, 1d, 2d, UpdatedCount.apply)
– ygor
Nov 16 '18 at 15:52