-
Notifications
You must be signed in to change notification settings - Fork 707
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make it easier to get around Tuple22 restrictions with Shapeless/HLists #1236
base: develop
Are you sure you want to change the base?
Changes from all commits
5f56f82
ee0f899
4af19fb
2432bc6
0631ac7
ab18131
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,8 +18,10 @@ package com.twitter.scalding | |
|
||
import cascading.tuple.TupleEntry | ||
import cascading.tuple.{ Tuple => CTuple } | ||
import shapeless._ | ||
import shapeless.ops.nat._ | ||
|
||
import scala.collection.breakOut | ||
import scala.collection.{GenTraversable, breakOut} | ||
|
||
/** | ||
* Typeclass to represent converting from cascading TupleEntry to some type T. | ||
|
@@ -51,6 +53,37 @@ trait LowPriorityTupleConverters extends java.io.Serializable { | |
} | ||
} | ||
|
||
/** | ||
* Based on `FromTraversable` from shapeless | ||
*/ | ||
trait FromTupleEntry[Out <: HList, I <: Nat] { | ||
def apply(l : TupleEntry) : Option[Out] | ||
} | ||
|
||
object FromTupleEntry { | ||
def apply[Out <: HList](implicit from: FromTupleEntry[Out, Nat._0]) = from | ||
|
||
implicit def hnilFromTupleEntry[T, I <: Nat](implicit toIntI : ToInt[I]) = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
new FromTupleEntry[HNil, I] { | ||
def apply(te : TupleEntry) = | ||
if(te.size() == toIntI()) Some(HNil) else None | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This forces the TupleEntry to have exactly the same size as the HList. I'm not sure if it needs to be this strict or if it would be ok for the TupleEntry to have more entries in it than the HList. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldn't we run |
||
} | ||
|
||
implicit def hlistFromTupleEntry[OutH, OutT <: HList, I <: Nat] | ||
(implicit | ||
flt : FromTupleEntry[OutT, Succ[I]], | ||
g : TupleGetter[OutH], | ||
toIntI : ToInt[I]) = | ||
new FromTupleEntry[OutH :: OutT, I] { | ||
def apply(te : TupleEntry) : Option[OutH :: OutT] = | ||
if(te.size() <= toIntI()) None | ||
else for( | ||
h <- new Some(g.get(te.getTuple, toIntI())); | ||
t <- flt(te) | ||
) yield h :: t | ||
} | ||
} | ||
|
||
object TupleConverter extends GeneratedTupleConverters { | ||
/** | ||
* Treat this TupleConverter as one for a superclass | ||
|
@@ -67,6 +100,23 @@ object TupleConverter extends GeneratedTupleConverters { | |
def arity[T](implicit tc: TupleConverter[T]): Int = tc.arity | ||
def of[T](implicit tc: TupleConverter[T]): TupleConverter[T] = tc | ||
|
||
import shapeless.ops.hlist._ | ||
|
||
implicit def hListConverter[H, T <: HList, N <: Nat] | ||
(implicit | ||
g: TupleGetter[H], | ||
len: Length.Aux[H :: T, N], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm still getting a sense of the design here, but any reason you don't constrain the |
||
toIntN : ToInt[N], | ||
fl : FromTupleEntry[H :: T, Nat._0]): TupleConverter[H :: T] = | ||
new TupleConverter[H :: T] { | ||
override def apply(te: TupleEntry): H :: T = { | ||
val l : Option[H :: T] = fl(te) | ||
l.get | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably not the best to just "get" the Option here. Open to other suggestions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, TupleConverter and TupleSetter are the places where types are added and removed before handing to cascading. It can't be type-safe at that point because cascading is not type-safe. There is really nothing you can do but call .get. That said, in a well typed scalding flow, that .get can never throw, so ultimately it would be safe (even though using the TupleConverter on its own, would not be safe). |
||
} | ||
|
||
override def arity: Int = toIntN() | ||
} | ||
|
||
/** | ||
* Copies the tupleEntry, since cascading may change it after the end of an | ||
* operation (and it is not safe to assume the consumer has not kept a ref | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,6 +65,21 @@ object TupleSetter extends GeneratedTupleSetters { | |
def arity[T](implicit ts: TupleSetter[T]): Int = ts.arity | ||
def of[T](implicit ts: TupleSetter[T]): TupleSetter[T] = ts | ||
|
||
import shapeless.ops.hlist._ | ||
import shapeless.ops.nat._ | ||
import shapeless._ | ||
|
||
implicit def hListSetter[L <: HList, N <: Nat, T <: Any] | ||
(implicit len: Length.Aux[L, N], ti: ToInt[N], tl: ToList[L, T]) = new TupleSetter[L] { | ||
|
||
override def apply(arg: L): CTuple = { | ||
val list = arg.toList[T].map(_.asInstanceOf[Object]) | ||
new CTuple(list:_*) | ||
} | ||
|
||
override def arity: Int = ti() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should be a val, to be sure we only call once. |
||
} | ||
|
||
//This is here for handling functions that return cascading tuples: | ||
implicit lazy val CTupleSetter: TupleSetter[CTuple] = new TupleSetter[CTuple] { | ||
override def apply(arg: CTuple) = new CTuple(arg) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this actually compatible with 2.10.3 -> 2.10.5?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding (based on the README) is that shapeless 2.1.0 is only compatible with 2.11.x and 2.10.4, but I might be mistaken. I think the relevant bits are as follows:
For shapeless 2.0.0, they have the following comments:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shapeless 2.1.0 has full cross-versions for 2.10 starting at 2.10.4 (e.g for 2.10.5), so it'd be better to use this:
So that we get the 2.10.5 version when Scalding updates to 2.10.5.
(I don't remember whether you need
%%
for full cross-versioning—I think either will work, and I find it a little clearer with%%
.)