Typeclass in a directive

Skip to first unread message

Mark Grey

Aug 2, 2016, 9:28:08 PM8/2/16
to spray.io User List
I'd like to define a directive that does some custom filtering on an object fetched further up the request chain.  Ideally I'd like the logic to be determined by an injectable typeclass.  Here's a really simple example to demonstrate the technique

trait Queryable[T] {
def matches(elem:T, q:String):Boolean

implicit object QueryInt extends Queryable[Int] {
def matches(elem:Int, q:String) = elem.toString.contains(q)

def directive[T : Queryable](l:Seq[T]) = parameter('query.?).flatMap {
    case Some(q) => provide(l.filter(implicitly[Queryable[T]].matches))
    case None => l

Naturally the context bounds here would expect a `Queryable[Int]` be implicitly available, and since it's implementation is part of the same mix in it should be usable.

However, I can't seem to get usage of this to compile, EG:

get {
(fetchIntFutureFunction){ints =>
(ints){ out =>

This fails to compile with: 

Missing parameter type
directive(ints){ out =>

Fiddling around with it, I figured out that no matter what I do, it expects me to pass the `Queryable[Int]` as a parameter.  But then I am returning a directive and not a route, and the compilation fails with:

Found spray.routing.Directive1[Seq[Int]]
Required: RequestContext => Unit

It seems whatever is wrong with my signature is making it oblivious to the inner route being passed.

Is there some other directive I should use than provide here?



Age Mooij

Aug 3, 2016, 6:13:46 AM8/3/16
to spray...@googlegroups.com
Mixing directives (i.e. higher order functions) with implicit parameter lists is very tricky because the compiler just sees a function that takes two parameter lists and then you call it with what the compiler can only interpret as two parameter lists. This is one of the reasons for the heavy use of the magnet pattern in spray routing.

In this case, it might be easier to move the filtering operation to non-directive code. Perhaps something like this:

import scala.concurrent._
import spray.routing._

trait Queryable[T] {
  def matches(elem: T, q: String): Boolean

object Queryable {
  implicit object QueryInt extends Queryable[Int] {
    def matches(elem: Int, q: String) = elem.toString.contains(q)

trait NonDirectivesMixin {
  def fetchIntFutureFunction: Future[Seq[Int]] = ???

  implicit class SeqWithOptionalFiltering[T: Queryable](ts: Seq[T]) {
    def optionalFilter(query: Option[String]): Seq[T] = query match {
      case Some(q) ⇒ ts.filter(t ⇒ implicitly[Queryable[T]].matches(t, q))
      case None    ⇒ ts

trait UserlistExperimentRoutes extends Directives with NonDirectivesMixin {
  implicit def ec: ExecutionContext

  def route = get {
    parameter('query.?) { q ⇒
      onSuccess(fetchIntFutureFunction) { ints ⇒
        // I used mkString for this example since I don't
        // have a marshaller[Seq[Int]] in scope

Hope this helps

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/fb823e95-9c6e-4efe-aea3-d452885525d1%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Mark Grey

Aug 3, 2016, 11:59:56 AM8/3/16
to spray.io User List
Thanks a bunch for the help!  Definitely confirms what I thought was the main obstacle. 

This is more or less what I ended up settling on, though my design probably needs some pretty significant refactors :/  Your approach with the future is much nicer than what I landed on :)

Reply all
Reply to author
0 new messages