Last active
February 13, 2017 15:51
-
-
Save caente/77ee1bff4471bd39cdc908eab1761c9f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package features | |
import shapeless._ | |
import ops.hlist.Selector | |
import syntax.std.function._ | |
import ops.function._ | |
import simulacrum._ | |
import scalaz._, Scalaz._ | |
object Main { | |
import utils.Find | |
import utils.Subset | |
import utils.applyContext | |
trait FeatureGenerator[X] { | |
type Context | |
def features(x: X, context: Context): Map[String, Int] | |
} | |
// features for String, each feature generator can process several contexts | |
// the implicit `Find`s are for the types it _might_ need in its internal generators | |
implicit def StringFeatures[L <: HList, F]( | |
implicit | |
si: Find[L, Int], | |
ss: Find[L, String], | |
sd: Find[L, Double] | |
) = new FeatureGenerator[String] { | |
type Context = L | |
private def featureGenerator1(x: String): (Int, Double) => Map[String, Int] = | |
(i, d) => | |
Map("ave" -> (d.toInt + i)) | |
private def featureGenerator2(x: String): (Int) => Map[String, Int] = | |
i => | |
Map("dev" -> i) | |
private def featureGenerator3(x: String): (String, Int) => Map[String, Int] = | |
(s, i) => | |
Map("sum" -> (s.size + i + x.size)) | |
//is possible to have generators with the same context, they will all be used | |
private def featureGenerator3_1(x: String): (String, Int) => Map[String, Int] = | |
(s, i) => | |
Map("sum2" -> (s.size + i * 2 + x.size)) | |
private def featureGenerator4(x: String): () => Map[String, Int] = | |
() => | |
Map("size" -> (x.size)) | |
def features(x: String, context: Context): Map[String, Int] = { | |
applyContext(context)(featureGenerator1(x)) ++ | |
applyContext(context)(featureGenerator2(x)) ++ | |
applyContext(context)(featureGenerator3(x)) ++ | |
applyContext(context)(featureGenerator3_1(x)) ++ | |
applyContext(context)(featureGenerator4(x)) | |
} | |
} | |
implicit def LabelessFeatures[L <: HList, F]( | |
implicit | |
si: Find[L, Int], | |
ss: Find[L, String] | |
) = new FeatureGenerator[FeatureGenerator.Labeless.type] { | |
type Context = L | |
private def featureGenerator1: (Int) => Map[String, Int] = | |
i => | |
Map("dev" -> i) | |
private def featureGenerator2: (String, Int) => Map[String, Int] = | |
(s, i) => | |
Map("sum" -> (s.size + i)) | |
private def featureGenerator3: () => Map[String, Int] = | |
() => | |
Map("size" -> 0) | |
def features(x: FeatureGenerator.Labeless.type, context: Context): Map[String, Int] = { | |
applyContext(context)(featureGenerator1) ++ | |
applyContext(context)(featureGenerator2) ++ | |
applyContext(context)(featureGenerator3) | |
} | |
} | |
object FeatureGenerator { | |
implicit class Ops[X](x: X) { | |
//instead of Product, we could do C <: BaseContext, | |
//to be more strict regarding the context possible values | |
def features[C <: Product, L <: HList](context: C)( | |
implicit | |
gen: Generic.Aux[C, L], | |
fg: FeatureGenerator[X] { type Context = L } | |
) = | |
fg.features(x, gen.to(context)) | |
def features[C](context: C)( | |
implicit | |
fg: FeatureGenerator[X] { type Context = C :: HNil } | |
) = | |
fg.features(x, context :: HNil) | |
} | |
case object Labeless | |
case class NoContext() | |
def emptyContext: NoContext = NoContext() | |
} | |
def main(args: Array[String]): Unit = { | |
import FeatureGenerator.Ops | |
// it works with tuples and case classes | |
assert( | |
"hi".features(FeatureGenerator.emptyContext) === Map("size" -> 2) | |
) | |
assert( | |
"hi".features(1) === Map("dev" -> 1, "size" -> 2) | |
) | |
assert( | |
"hi".features(1, 2d) === Map("ave" -> 3, "dev" -> 1, "size" -> 2) | |
) | |
assert( | |
"hi".features("a", 1) === Map("sum" -> 4, "sum2" -> 5, "dev" -> 1, "size" -> 2) | |
) | |
// the order of the arguments doesn't matter | |
assert( | |
"hi".features(2d, 1, "a") === Map("sum" -> 4, "sum2" -> 5, "dev" -> 1, "ave" -> 3, "size" -> 2) | |
) | |
// feature generators with no X | |
assert( | |
FeatureGenerator.Labeless.features(FeatureGenerator.emptyContext) === Map("size" -> 0) | |
) | |
assert( | |
FeatureGenerator.Labeless.features(1) === Map("dev" -> 1, "size" -> 0) | |
) | |
assert( | |
FeatureGenerator.Labeless.features("a", 1) === Map("sum" -> 2, "dev" -> 1, "size" -> 0) | |
) | |
// the order of the arguments doesn't matter | |
assert( | |
FeatureGenerator.Labeless.features(1, "a") === Map("sum" -> 2, "dev" -> 1, "size" -> 0) | |
) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package features | |
import shapeless._ | |
import ops.hlist.Selector | |
import syntax.std.function._ | |
import ops.function._ | |
import simulacrum._ | |
import scalaz._, Scalaz._ | |
object utils { | |
def applyContext[L <: HList, Args <: HList, F, R](c: L)(f: F)( | |
implicit | |
fp: FnToProduct.Aux[F, Args => R], | |
subset: Subset.Aux[L, Args], | |
mr: Monoid[R] | |
): R = | |
subset(c).map { | |
args => | |
f.toProduct(args) | |
}.getOrElse(mr.zero) | |
trait Find[L <: HList, A] { | |
def find(l: L): Option[A] | |
} | |
object Find { | |
def apply[A, L <: HList](implicit f: Find[L, A]) = f | |
implicit class Ops[L <: HList](l: L) { | |
def find[A](implicit f: Find[L, A]) = f.find(l) | |
} | |
implicit def hconsFound[A, H, T <: HList](implicit ev: H =:= A) = new Find[H :: T, A] { | |
def find(l: H :: T) = Some(l.head) | |
} | |
implicit def hconsNotFound[A, H, T <: HList](implicit f: Find[T, A]) = new Find[H :: T, A] { | |
def find(l: H :: T) = f.find(l.tail) | |
} | |
implicit def hnil[A] = new Find[HNil, A] { | |
def find(l: HNil) = None | |
} | |
} | |
import Find.Ops | |
trait Subset[L <: HList] { | |
type Out <: HList | |
def apply(l: L): Option[Out] | |
} | |
object Subset { | |
type Aux[L <: HList, S <: HList] = Subset[L] { type Out = S } | |
def apply[L <: HList, S <: HList](implicit f: Subset.Aux[L, S]) = f | |
implicit def hcons[L <: HList, H, T <: HList]( | |
implicit | |
find: Find[L, H], | |
subset: Lazy[Subset.Aux[L, T]] | |
) = new Subset[L] { | |
type Out = H :: T | |
def apply(l: L) = | |
(l.find[H] |@| subset.value(l)) { | |
(h, t) => h :: t | |
} | |
} | |
implicit def hnil[L <: HList]: Subset.Aux[L, HNil] = new Subset[L] { | |
type Out = HNil | |
def apply(l: L) = Some(HNil) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
some comments:
1 - if two feature generators produce the same keys in the map, the latest one will replace the previous, but that's an issue with Map
2 - for now the only way to provide context of one element is by wrapping it up in a case class
3 - I'd like to add tests to the typeclasses