Overloading methods using magnet pattern.

214 views
Skip to first unread message

Colin Bester

unread,
Jul 1, 2016, 9:27:20 AM7/1/16
to spray.io User List
I am trying to add paging functionality with various options while providing easy extensibility later on.

In my route I have a pagination directive that extracts query parameters and depending on which exist, wraps them in suitable class (FromDate, ToDate etc). These classes are derived from a base class called "DateSelection" and my Pagination directive returns Directive1[DateSelection] (.

Route:

get {
 pagination
{ page =>
    format
{ format =>
   
...        



Pagination Directive snippet:

def pagination: Directive1[DateSelection] =
 parameterMap
.flatMap { params =>
     
(params.get(FromDateKey).map(_.toString), params.get(ToDateKey).map(_.toString), params.get(LimitKey).map(_.toInt)) match {
       
case (Some(fromDate), Some(toDate), limit) => provide(FromToDate(fromDate, toDate, limit))
       
case (Some(fromDate), None, limit) => provide(FromDate(fromDate, limit))
       
case (None, Some(toDate), limit) => provide(ToDate(toDate, limit))
       
case _             => reject(MalformedPaginationRejection("Invalid pagination data", None))
     
}
   
}
   
...
}  


As operation is different for each of the paging functions, I have used "overloaded functions" via the magnet pattern with thought being that as time goes by one could add to functionality and introduce new query parameters.

Most probably totally overkill for this particular issue, but my head is stubbornly locked into this kind of solution and I need it to run its course.

My problem is that for magnet pattern implicits (shown in implicits def's below) to work I need to add a wrapper that takes response from pagination directive and via pattern matching return the actual type (FromDate, ToDate) as opposed to base type DateSelection so that I can pass in the actual type.

This last piece causes me heartburn as it goes against the concept of making code extensible. To me it's logical that if I want to introduce a new parameter (say reverse) that I add a new class, add new implicit and I am good to go, I should have to also remember to go to my wrapper function and add a new pattern matching case to fetch actual class instance.

page match  {
 
case f:FromDate => UsageLoggers.fetchLogs(userIds, deviceIds, recipeIds, f, MongoDBObject.empty)
 
case t:ToDate => UsageLoggers.fetchLogs(userIds, deviceIds, recipeIds, t, MongoDBObject.empty)
 
case ft:FromToDate => UsageLoggers.fetchLogs(userIds, deviceIds, recipeIds, ft, MongoDBObject.empty)                          
}


Again, I know this is overkill, but I need to scratch the magnet pattern itch I have and know there is either a 'smack me on back of head' or learning moment but I'd appreciate any ideas/patterns on how to resolve the problem of identifying the actual type and pass to magnet implicit.

def fetchLogs(magnet: PaginationMagnet): Future[PagedUsage] = magnet()      


sealed trait DateSelection
case class FromDate(fromDate:DateTime, limit:Option[Int]) extends DateSelection
case class ToDate(toDate:DateTime, limit:Option[Int]) extends DateSelection
case class FromToDate(fromDate:DateTime, toDate:DateTime, limit:Option[Int]) extends DateSelection


sealed trait PaginationMagnet {
 
def apply(): Future[PagedUsage]
}


implicit def implicitFromDate(tuple: (List[ObjectId],..., FromDate)) = new PaginationMagnet {
 
def apply(): Future[PagedUsage] = { .. }
}


implicit def implicitToDate(tuple: (List[ObjectId], ...., ToDate)) = new PaginationMagnet {
 
def apply(): Future[PagedUsage] = {..}
}

Age Mooij

unread,
Jul 1, 2016, 10:36:36 AM7/1/16
to spray...@googlegroups.com
Hi Colin. 
This sounds interesting but I can't seem to extract any actual question(s) out of your message. Could you please summarize what specifically you are asking?

I assume you have looked at the many instances of the Magnet pattern in the Spray sources themselves? If not, they might give you some insights into how to build complicated stuff with the magnet pattern.

Age

--
You received this message because you are subscribed to the Google Groups "spray.io User List" group.
To unsubscribe from this group and stop receiving emails from it, send an email to spray-user+...@googlegroups.com.
Visit this group at https://groups.google.com/group/spray-user.
To view this discussion on the web visit https://groups.google.com/d/msgid/spray-user/a8160146-3851-4735-ad58-f9716a40ca71%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Message has been deleted

Colin Bester

unread,
Jul 1, 2016, 10:54:07 AM7/1/16
to spray.io User List
Hi Age, indeed I am battling to define the actual question in clear manner, which is why the question is so long.

The crux of the matter is I have a method returning one of several case classes (used as a type). Each of these classes extends base type class.

Example
trait Animal

case class Dog(...) extends BaseType
case class Cat(...) extends BaseType

I then have another function which returns either dog or cat case class but as parent class Animal

def getChoice(..):Animal = {
some logic...
Cat(....)
}

Then using the results of this choice I want to call one of several overloaded functions (hence the magnet pattern) but for simplicity think of simple overloading from days before Scala...

def doSomething(arg1:String, arg2:Int, dog:Dog)
def doSomething(arg1:String, arg2:Int, cat:Cat)

I can't do this as my result from getChoice is of type Animal. One way around this is to do pattern matching on result and create either Dog or Cat class and call doSomething, like:

getChoice() match {
case x:Dog => doSomething("1", 2, x)
case x:Cat => doSomething("1", 2, x)
}

But this is horrid as it's another touch point (and not a logical one) that requires updating of code if adding functionality.

So in short (okay, not so short) I am looking for way to (and I hate typing this) determine actual class from getChoice in order to call doSomething - hope this helps a little more...

Thanks for responding.

Age Mooij

unread,
Jul 1, 2016, 11:12:31 AM7/1/16
to spray...@googlegroups.com
Perhaps it would help if you could explain why you want to call these specific functions? Without knowing the context, it is hard to et a sense for the correct answer(s).

As somebody who has done a lot of OO development before discovering functional, my first instinct would be to deal with this by putting the relevant "doSomething" method inside the actual Animal subclasses and just calling that method. 

Or put the method on a specific wrapper and return the wrapper. 

Or you could use a typeclass instance per Animal subclass and put these into the companion objects of the Animal subclasses.

Does that make any sense?
Age

Colin Bester

unread,
Jul 1, 2016, 12:12:39 PM7/1/16
to spray...@googlegroups.com
I'll have to think how to better describe what I want to do as it's clear to me :-)

I am wanting to take parsed in parameters via a directive and then call appropriate overloaded function depending on parameter set. 

Sent from my iPhone
You received this message because you are subscribed to a topic in the Google Groups "spray.io User List" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/spray-user/WLFUnBYeuS8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to spray-user+...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages