0

I'm trying to wrap my head around how Spray has implemented their Directives, and in particular the Parameter extraction DSL.

I understand the magnet pattern (barely) but am stuck on how the ParamDefMagnet and ParamDefMagnet2 work together.

def parameter(pdm: ParamDefMagnet): pdm.Out = pdm()

trait ParamDefMagnet {
  type Out
  def apply(): Out
}

trait ParamDefMagnet2[T] {
  type Out
  def apply(value: T): Out
}

type ParamDefMagnetAux[A, B] = ParamDefMagnet2[A] { type Out = B }
  def ParamDefMagnetAux[A, B](f: A ⇒ B) = new ParamDefMagnet2[A] { type Out = B; def apply(value: A) = f(value) }

I'm trying to work out how a ParamDefManget2 is implicitly converted to a ParamDefMagnet by the the below implicit method.

object ParamDefMagnet {
  implicit def apply[T](value: T)(implicit pdm2: ParamDefMagnet2[T]) = new ParamDefMagnet {
    type Out = pdm2.Out
    def apply() = pdm2(value)
  }
}

If i call parameter("name"), how is "name" implicitly converted to a ParamDefMagnet? And if it converts it to a ParamDefMagnet2 first, then where does value: T come from in order to convert it to a ParamDefMagnet?

Upio
  • 1,364
  • 1
  • 12
  • 27
  • Have you read the Spray blog post that explains the Magnet pattern? That does a good job of explaining the reasoning behind it plus details on how they are implemented. See http://spray.io/blog/2012-12-13-the-magnet-pattern/ – Age Mooij Apr 24 '15 at 11:08
  • I have read it, but it didn't go into detail about the use of two magnets, which is how spray parameter extraction works. But now that i've dug around, it's definitely worth me re-reading this as I didn't get it all on my first go through. Thanks! – Upio Apr 24 '15 at 20:19

1 Answers1

0

So after digging around with examples, I think i've finally got to the bottom of how the parameter function works:

def parameter(pdm: ParamDefMagnet): pdm.Out = pdm()

An example for extracting a parameter of type String:

val p: Directive1[String] = parameter("name")

// we can then apply the function with the extracted name
p { name => 
   // stuff
}

Spray uses a bunch of implicit conversions but basically, if you have a String and a String => Directive1[String], you can construct a () => Directive1[String]:

// Our String => Directive1[String]
val pdm2: ParamDefMagnet2[String] { type Out = Directive1[String] } = ParamDefMagnet2.fromString

// Our () => Directive1[String]
val pdm: ParamDefMagnet { type Out = Directive1[String] } = new ParamDefMagnet {
  type Out = Directive1[String]
  def apply() = pdm2("name")
}
val directive: Directive1[String] = pdm()
// equivalent to:
val directive2: Directive1[String] = parameter("name")

All of this is what constitutes the simple parameter("name") call:

val p: Directive1[String] = parameter("name")

For how a Directive1[String] is applied in a DSL-ey way, see How do directives work in Spray?

Community
  • 1
  • 1
Upio
  • 1,364
  • 1
  • 12
  • 27
  • From what I glanced this is basically correct. To amend this information see this gist which contains the expansion of the `parameter` call including all the implicit invocations. I also included the case for extracting two parameters where you can see that things get quite unwieldy... https://gist.github.com/jrudolph/a16af22fcbb3954be1b0 – jrudolph Apr 29 '15 at 06:37