From 33ddabbc9fc46a78685e147461e80c74a462b706 Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 15 Oct 2014 10:06:36 +0200 Subject: [PATCH] =htp refactor tuple join to foldLeft-based implementation, unify tuple operations --- .../akka/http/server/util/TupleOpsSpec.scala | 27 ++++--- ...ConstructFromTupleInstances.scala.template | 2 +- .../util/HeadTailInstances.scala.template | 25 ------- ...=> TupleAppendOneInstances.scala.template} | 7 +- .../util/TupleFoldInstances.scala.template | 23 ++++-- .../scala/akka/http/server/Directive.scala | 8 +- .../scala/akka/http/server/PathMatcher.scala | 7 +- .../http/server/util/BinaryPolyFunc.scala | 29 +++++++ .../http/server/util/ConstructFromTuple.scala | 8 +- .../scala/akka/http/server/util/Join.scala | 75 ------------------- .../scala/akka/http/server/util/Tuple.scala | 5 ++ .../akka/http/server/util/TupleOps.scala | 72 ++++++++++-------- 12 files changed, 124 insertions(+), 164 deletions(-) delete mode 100644 akka-http/src/main/boilerplate/akka/http/server/util/HeadTailInstances.scala.template rename akka-http/src/main/boilerplate/akka/http/server/util/{AppendOneInstances.scala.template => TupleAppendOneInstances.scala.template} (88%) create mode 100644 akka-http/src/main/scala/akka/http/server/util/BinaryPolyFunc.scala delete mode 100644 akka-http/src/main/scala/akka/http/server/util/Join.scala diff --git a/akka-http-tests/src/test/scala/akka/http/server/util/TupleOpsSpec.scala b/akka-http-tests/src/test/scala/akka/http/server/util/TupleOpsSpec.scala index 34bd3d491f..28bccf2a6e 100644 --- a/akka-http-tests/src/test/scala/akka/http/server/util/TupleOpsSpec.scala +++ b/akka-http-tests/src/test/scala/akka/http/server/util/TupleOpsSpec.scala @@ -4,23 +4,26 @@ package akka.http.server.util -import org.scalatest.{ Matchers, FreeSpec } +import org.scalatest.{ Matchers, WordSpec } -class TupleOpsSpec extends FreeSpec with Matchers { +class TupleOpsSpec extends WordSpec with Matchers { import TupleOps._ - "The TupleOps should" - { + "The TupleOps" should { - "support folding over tuples using a binary poly-function" - { - - "example 1" in { - object Funky extends Poly2 { - implicit def step1 = at[Double, Int](_ + _) - implicit def step2 = at[Double, Symbol]((d, s) ⇒ (d + s.name.tail.toInt).toByte) - implicit def step3 = at[Byte, String]((byte, s) ⇒ byte + s.toLong) - } - (1, 'X2, "3").foldLeft(0.0)(Funky) shouldEqual 6L + "support folding over tuples using a binary poly-function" in { + object Funky extends BinaryPolyFunc { + implicit def step1 = at[Double, Int](_ + _) + implicit def step2 = at[Double, Symbol]((d, s) ⇒ (d + s.name.tail.toInt).toByte) + implicit def step3 = at[Byte, String]((byte, s) ⇒ byte + s.toLong) } + (1, 'X2, "3").foldLeft(0.0)(Funky) shouldEqual 6L + } + + "support joining tuples" in { + (1, 'X2, "3") join () shouldEqual (1, 'X2, "3") + () join (1, 'X2, "3") shouldEqual (1, 'X2, "3") + (1, 'X2, "3") join (4.0, 5L) shouldEqual (1, 'X2, "3", 4.0, 5L) } } } \ No newline at end of file diff --git a/akka-http/src/main/boilerplate/akka/http/server/util/ConstructFromTupleInstances.scala.template b/akka-http/src/main/boilerplate/akka/http/server/util/ConstructFromTupleInstances.scala.template index 5a1e9b4bf5..019ede1614 100644 --- a/akka-http/src/main/boilerplate/akka/http/server/util/ConstructFromTupleInstances.scala.template +++ b/akka-http/src/main/boilerplate/akka/http/server/util/ConstructFromTupleInstances.scala.template @@ -4,7 +4,7 @@ package akka.http.server.util -trait ConstructFromTupleInstances { +private[util] abstract class ConstructFromTupleInstances { [#implicit def instance1[[#T1#], R](construct: ([#T1#]) => R): ConstructFromTuple[Tuple1[[#T1#]], R] = new ConstructFromTuple[Tuple1[[#T1#]], R] { def apply(tup: Tuple1[[#T1#]]): R = construct([#tup._1#]) diff --git a/akka-http/src/main/boilerplate/akka/http/server/util/HeadTailInstances.scala.template b/akka-http/src/main/boilerplate/akka/http/server/util/HeadTailInstances.scala.template deleted file mode 100644 index bf5e95def6..0000000000 --- a/akka-http/src/main/boilerplate/akka/http/server/util/HeadTailInstances.scala.template +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2009-2014 Typesafe Inc. - */ - -package akka.http.server.util - -private[util] abstract class HeadTailInstances { - type Aux[L, H0, T0] = HeadTail[L] { type H = H0; type T = T0 } - - implicit def headTail1[T1]: Aux[Tuple1[T1], T1, Unit] = - new HeadTail[Tuple1[T1]] { - type H = T1 - type T = Unit - - def apply(in: Tuple1[T1]): (T1, Unit) = (in._1, ()) - } - [1..21#implicit def headTail2[_H, [#T1#]]: Aux[Tuple2[_H, [#T1#]], _H, Tuple1[[#T1#]]] = - new HeadTail[Tuple2[_H, [#T1#]]] { - type H = _H - type T = Tuple1[[#T1#]] - - def apply(in: Tuple2[_H, [#T1#]]): (_H, Tuple1[[#T1#]]) = (in._##1, Tuple1([#in._2#])) - }# - ] -} \ No newline at end of file diff --git a/akka-http/src/main/boilerplate/akka/http/server/util/AppendOneInstances.scala.template b/akka-http/src/main/boilerplate/akka/http/server/util/TupleAppendOneInstances.scala.template similarity index 88% rename from akka-http/src/main/boilerplate/akka/http/server/util/AppendOneInstances.scala.template rename to akka-http/src/main/boilerplate/akka/http/server/util/TupleAppendOneInstances.scala.template index 2a0bb36d92..21c810646a 100644 --- a/akka-http/src/main/boilerplate/akka/http/server/util/AppendOneInstances.scala.template +++ b/akka-http/src/main/boilerplate/akka/http/server/util/TupleAppendOneInstances.scala.template @@ -4,19 +4,20 @@ package akka.http.server.util -private[util] abstract class AppendOneInstances { +import TupleOps.AppendOne + +private[util] abstract class TupleAppendOneInstances { type Aux[P, S, Out0] = AppendOne[P, S] { type Out = Out0 } implicit def append0[T1]: Aux[Unit, T1, Tuple1[T1]] = new AppendOne[Unit, T1] { type Out = Tuple1[T1] - def apply(prefix: Unit, last: T1): Tuple1[T1] = Tuple1(last) } + [1..21#implicit def append1[[#T1#], L]: Aux[Tuple1[[#T1#]], L, Tuple2[[#T1#], L]] = new AppendOne[Tuple1[[#T1#]], L] { type Out = Tuple2[[#T1#], L] - def apply(prefix: Tuple1[[#T1#]], last: L): Tuple2[[#T1#], L] = Tuple2([#prefix._1#], last) }# ] diff --git a/akka-http/src/main/boilerplate/akka/http/server/util/TupleFoldInstances.scala.template b/akka-http/src/main/boilerplate/akka/http/server/util/TupleFoldInstances.scala.template index a73c72d900..ba07aee1bf 100644 --- a/akka-http/src/main/boilerplate/akka/http/server/util/TupleFoldInstances.scala.template +++ b/akka-http/src/main/boilerplate/akka/http/server/util/TupleFoldInstances.scala.template @@ -5,18 +5,29 @@ package akka.http.server.util import TupleOps.FoldLeft -import TupleOps.FoldLeft.Aux -import Poly2.Case +import BinaryPolyFunc.Case private[util] abstract class TupleFoldInstances { + type Aux[In, T, Op, Out0] = FoldLeft[In, T, Op] { type Out = Out0 } + + implicit def t0[In, Op]: Aux[In, Unit, Op, In] = + new FoldLeft[In, Unit, Op] { + type Out = In + def apply(zero: In, tuple: Unit) = zero + } + + implicit def t1[In, A, Op](implicit f: Case[In, A, Op]): Aux[In, Tuple1[A], Op, f.Out] = + new FoldLeft[In, Tuple1[A], Op] { + type Out = f.Out + def apply(zero: In, tuple: Tuple1[A]) = f(zero, tuple._1) + } + [2..22#implicit def t1[In, [2..#T0#], X, T1, Op](implicit fold: Aux[In, Tuple0[[2..#T0#]], Op, X], f: Case[X, T1, Op]): Aux[In, Tuple1[[#T1#]], Op, f.Out] = new FoldLeft[In, Tuple1[[#T1#]], Op] { type Out = f.Out - def apply(zero: In, tuple: Tuple1[[#T1#]]) = { - val ([#v1#]) = tuple - f(fold(zero, Tuple0([2..#v0#])), v1) - } + def apply(zero: In, t: Tuple1[[#T1#]]) = + f(fold(zero, Tuple0([2..#t._0#])), t._1) }# ] } \ No newline at end of file diff --git a/akka-http/src/main/scala/akka/http/server/Directive.scala b/akka-http/src/main/scala/akka/http/server/Directive.scala index 21c64b501d..e7c21a534b 100644 --- a/akka-http/src/main/scala/akka/http/server/Directive.scala +++ b/akka-http/src/main/scala/akka/http/server/Directive.scala @@ -5,23 +5,21 @@ package akka.http.server import scala.concurrent.Future - +import scala.collection.immutable import akka.http.server.directives.RouteDirectives import akka.http.server.util._ -import scala.collection.immutable - trait ConjunctionMagnet[L] { type Out def apply(underlying: Directive[L]): Out } object ConjunctionMagnet { - implicit def fromDirective[L, R](other: Directive[R])(implicit join: Join[L, R]) = + implicit def fromDirective[L, R](other: Directive[R])(implicit join: TupleOps.Join[L, R]) = new ConjunctionMagnet[L] { type Out = Directive[join.Out] def apply(underlying: Directive[L]): Out = - new Directive[join.Out]()(join.OutIsTuple) { + new Directive[join.Out]()(Tuple.yes /* we know that join will only ever produce tuples*/ ) { def tapply(f: join.Out ⇒ Route) = underlying.tapply { prefix ⇒ other.tapply { suffix ⇒ diff --git a/akka-http/src/main/scala/akka/http/server/PathMatcher.scala b/akka-http/src/main/scala/akka/http/server/PathMatcher.scala index 354cf8d934..5807c92d45 100644 --- a/akka-http/src/main/scala/akka/http/server/PathMatcher.scala +++ b/akka-http/src/main/scala/akka/http/server/PathMatcher.scala @@ -7,7 +7,8 @@ package akka.http.server import java.util.UUID import scala.util.matching.Regex import scala.annotation.tailrec -import akka.http.server.util.{ Tuple, Join } +import akka.http.server.util.Tuple +import akka.http.server.util.TupleOps._ import akka.http.model.Uri.Path import akka.http.util._ import directives.NameReceptacle @@ -21,7 +22,7 @@ abstract class PathMatcher[L](implicit val ev: Tuple[L]) extends (Path ⇒ PathM def / : PathMatcher[L] = this ~ PathMatchers.Slash - def /[R](other: PathMatcher[R])(implicit prepender: Join[L, R]): PathMatcher[prepender.Out] = + def /[R](other: PathMatcher[R])(implicit join: Join[L, R]): PathMatcher[join.Out] = this ~ PathMatchers.Slash ~ other def |[R >: L: Tuple](other: PathMatcher[_ <: R]): PathMatcher[R] = @@ -30,7 +31,7 @@ abstract class PathMatcher[L](implicit val ev: Tuple[L]) extends (Path ⇒ PathM } def ~[R](other: PathMatcher[R])(implicit join: Join[L, R]): PathMatcher[join.Out] = { - import join.OutIsTuple + implicit def joinProducesTuple = Tuple.yes[join.Out] transform(_.andThen((restL, valuesL) ⇒ other(restL).map(join(valuesL, _)))) } diff --git a/akka-http/src/main/scala/akka/http/server/util/BinaryPolyFunc.scala b/akka-http/src/main/scala/akka/http/server/util/BinaryPolyFunc.scala new file mode 100644 index 0000000000..2e613ab34c --- /dev/null +++ b/akka-http/src/main/scala/akka/http/server/util/BinaryPolyFunc.scala @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package akka.http.server.util + +/** + * Allows the definition of binary poly-functions (e.g. for folding over tuples). + * + * Note: the poly-function implementation seen here is merely a stripped down version of + * what Miles Sabin made available with his awesome shapeless library. All credit goes to him! + */ +trait BinaryPolyFunc { + def at[A, B] = new CaseBuilder[A, B] + class CaseBuilder[A, B] { + def apply[R](f: (A, B) ⇒ R) = new BinaryPolyFunc.Case[A, B, BinaryPolyFunc.this.type] { + type Out = R + def apply(a: A, b: B) = f(a, b) + } + } +} + +object BinaryPolyFunc { + sealed trait Case[A, B, Op] { + type Out + def apply(a: A, b: B): Out + } +} + diff --git a/akka-http/src/main/scala/akka/http/server/util/ConstructFromTuple.scala b/akka-http/src/main/scala/akka/http/server/util/ConstructFromTuple.scala index 9b8f3bc2c7..5c6036941b 100644 --- a/akka-http/src/main/scala/akka/http/server/util/ConstructFromTuple.scala +++ b/akka-http/src/main/scala/akka/http/server/util/ConstructFromTuple.scala @@ -4,7 +4,9 @@ package akka.http.server.util -trait ConstructFromTuple[T, R] { - def apply(t: T): R -} +/** + * Constructor for instances of type ``R`` which can be created from a tuple of type ``T``. + */ +trait ConstructFromTuple[T, R] extends (T ⇒ R) + object ConstructFromTuple extends ConstructFromTupleInstances diff --git a/akka-http/src/main/scala/akka/http/server/util/Join.scala b/akka-http/src/main/scala/akka/http/server/util/Join.scala deleted file mode 100644 index 6548f153bd..0000000000 --- a/akka-http/src/main/scala/akka/http/server/util/Join.scala +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2009-2014 Typesafe Inc. - */ - -package akka.http.server.util - -/** - * `Join` is implicit evidence that tuples `P` and `S` can be joined into tuple `Out` - */ -trait Join[P, S] { - type Out - implicit def OutIsTuple: Tuple[Out] = null // we know that Join only produces Tuples - def apply(prefix: P, suffix: S): Out -} - -object Join extends LowLevelJoinImplicits { - // O(1) shortcut for the Join[Unit, T] case to avoid O(n) runtime in this case - implicit def join0P[T]: Aux[Unit, T, T] = - new Join[Unit, T] { - type Out = T - def apply(prefix: Unit, suffix: T): Out = suffix - } -} - -trait LowLevelJoinImplicits { - type Aux[P, S, Out0] = Join[P, S] { type Out = Out0 } - - /* - O(b.length) joining algorithm: - - def headTail(l: List[T]): (T, List[T]) - def append(l: List[T], t: T): List[T] - - def join(a: List[T], b: List[T]): List[T] = - if (b.isEmpty) a - else { - val (head, tail) = removeFirst(b) - join(append(a, head), tail) - } - - The advantage of using this simple type-level algorithm is that we don't need to define 22 * 22 implicits for joining - but only 22 + 22 (22 for each HeadTail and AppendOne). - - The idea is that `b` will usually be shorter than `a` because `Directive.&` is left-associative so the bigger - lists will be usually created on the left side. - */ - - implicit def joinP0[P]: Aux[P, Unit, P] = - new Join[P, Unit] { - type Out = P - def apply(prefix: P, suffix: Unit): P = prefix - } - - implicit def joinN[A, B, H, T, C](implicit r: HeadTail.Aux[B, H, T], a: AppendOne.Aux[A, H, C], inner: Join[C, T]): Aux[A, B, inner.Out] = - new Join[A, B] { - type Out = inner.Out - def apply(prefix: A, suffix: B): Out = { - val (h, t) = r(suffix) - inner(a(prefix, h), t) - } - } -} - -trait AppendOne[P, S] { - type Out - def apply(prefix: P, last: S): Out -} -object AppendOne extends AppendOneInstances - -trait HeadTail[L] { - type H - type T - def apply(in: L): (H, T) -} -object HeadTail extends HeadTailInstances \ No newline at end of file diff --git a/akka-http/src/main/scala/akka/http/server/util/Tuple.scala b/akka-http/src/main/scala/akka/http/server/util/Tuple.scala index 25777b0185..cf386d36d9 100644 --- a/akka-http/src/main/scala/akka/http/server/util/Tuple.scala +++ b/akka-http/src/main/scala/akka/http/server/util/Tuple.scala @@ -10,6 +10,11 @@ package akka.http.server.util sealed trait Tuple[T] object Tuple { + /** + * Used to provide "is-Tuple" evidence where we know that a given value must be a tuple. + */ + def yes[T]: Tuple[T] = null + implicit def forNothing[A]: Tuple[Nothing] = null implicit def forUnit[A]: Tuple[Unit] = null implicit def forTuple1[A]: Tuple[Tuple1[A]] = null diff --git a/akka-http/src/main/scala/akka/http/server/util/TupleOps.scala b/akka-http/src/main/scala/akka/http/server/util/TupleOps.scala index 8ecf22fe86..4a5d39fa6e 100644 --- a/akka-http/src/main/scala/akka/http/server/util/TupleOps.scala +++ b/akka-http/src/main/scala/akka/http/server/util/TupleOps.scala @@ -7,48 +7,58 @@ package akka.http.server.util class TupleOps[T](val tuple: T) extends AnyVal { import TupleOps._ - def foldLeft[In](zero: In)(op: Poly2)(implicit fold: FoldLeft[In, T, op.type]): fold.Out = fold(zero, tuple) + /** + * Appends the given value to the tuple producing a tuple of arity n + 1. + */ + def append[S](value: S)(implicit ao: AppendOne[T, S]): ao.Out = ao(tuple, value) + + /** + * Left-Folds over the tuple using the given binary poly-function. + */ + def foldLeft[In](zero: In)(op: BinaryPolyFunc)(implicit fold: FoldLeft[In, T, op.type]): fold.Out = fold(zero, tuple) + + /** + * Appends the given tuple to the underlying tuple producing a tuple of arity n + m. + */ + def join[S](suffixTuple: S)(implicit join: Join[T, S]): join.Out = join(tuple, suffixTuple) } object TupleOps { implicit def enhanceTuple[T: Tuple](tuple: T) = new TupleOps(tuple) + trait AppendOne[P, S] { + type Out + def apply(prefix: P, last: S): Out + } + object AppendOne extends TupleAppendOneInstances + trait FoldLeft[In, T, Op] { type Out def apply(zero: In, tuple: T): Out } - object FoldLeft extends TupleFoldInstances { - import Poly2.Case + object FoldLeft extends TupleFoldInstances - type Aux[In, T, Op, Out0] = FoldLeft[In, T, Op] { type Out = Out0 } - - implicit def t0[In, Op]: Aux[In, Unit, Op, In] = - new FoldLeft[In, Unit, Op] { - type Out = In - def apply(zero: In, tuple: Unit) = zero - } - - implicit def t1[In, A, Op](implicit f: Case[In, A, Op]): Aux[In, Tuple1[A], Op, f.Out] = - new FoldLeft[In, Tuple1[A], Op] { - type Out = f.Out - def apply(zero: In, tuple: Tuple1[A]) = f(zero, tuple._1) - } + trait Join[P, S] { + type Out + def apply(prefix: P, suffix: S): Out } -} - -trait Poly2 { - def at[A, B] = new CaseBuilder[A, B] - class CaseBuilder[A, B] { - def apply[R](f: (A, B) ⇒ R) = new Poly2.Case[A, B, Poly2.this.type] { - type Out = R - def apply(a: A, b: B) = f(a, b) + object Join extends LowLevelJoinImplicits { + // O(1) shortcut for the Join[Unit, T] case to avoid O(n) runtime in this case + implicit def join0P[T] = + new Join[Unit, T] { + type Out = T + def apply(prefix: Unit, suffix: T): Out = suffix + } + // we implement the join by folding over the suffix with the prefix as growing accumulator + object Fold extends BinaryPolyFunc { + implicit def step[T, A](implicit append: AppendOne[T, A]) = at[T, A](append(_, _)) } } -} -object Poly2 { - trait Case[A, B, Op] { - type Out - def apply(a: A, b: B): Out + sealed abstract class LowLevelJoinImplicits { + implicit def join[P, S](implicit fold: FoldLeft[P, S, Join.Fold.type]) = + new Join[P, S] { + type Out = fold.Out + def apply(prefix: P, suffix: S): Out = fold(prefix, suffix) + } } -} - +} \ No newline at end of file