Пользовательское API на базе
Shapeless
Челышов Вадим - qtankle@gmail.com
hydrosphere.io
Scalalaz Podcast
INTRO
Scala styles
better java
std scala fp
pure functional
generic + type level
Generic + type level
Shapeless
Generic + type level
akka-http
slick
finch
SBT
Spark app
Spark app
spark/bin/spark-submit 
--class "SimpleApp" 
--master local[4] 
target/scala-2.11/simple-project_2.11-1.0.jar
Frontend
Frontend
Frontend
Mist
https://github.com/Hydrospheredata/mist
Library Api
object UserJob extends Job {
def execute(a: String, b: Int): Map[String, Any] = {
val wordsToCount = sc.textFile("hdfs://...").flatMap(line
wordsToCount
}
}
Минусы
serialization/deserialization
нельзя override def execute
inject spark context
Прототип
object MyJob extends Job[R] {
override def defineJob: JobDef[R] = ???
}
Прототип
override def define = {
args(
arg[Int]("x"),
arg[String]("str")
)
}
Прототип
object MyJob extends Job[Int] {
override def define = {
args(arg[String]("path")).withSparkContext(
(p: String, sc: SparkContext) => {
sc.textFile("hdfs://" + p)
})
}
}
Akka-http
val route =
parameters('foo, 'bar) { (foo, bar) =>
complete(s"Hello $foo $bar")
}
Finch
val route = get("foo" :: path[String]) { s: String =>
Ok(s"Hello $foo")
}
Sbt
packageBin in Compile <<= (name, organization, version) map {
(n, o, v) => file(o + "-" + n + "-" + v + ".jar")
}
Fake it till you make it
Arg
trait Arg[A]{
def extract(ctx: Ctx): Option[A]
}
Arg
def arg[A](name: String): Arg[A]
def arg[A](name: String, default: A): Arg[A]
spread your boilerplate
def args[A](a1: Arg[A]): Args1[A]
def args[A](a1: Arg[A], a2: Arg[B]): Args2[A, B]
и еще
class Args1[A](a1: Arg[A]) {
def withContext[Res](
f: (A, SparkContext) => Res): JobDef[Res]
}
class Args2[A, B](a1: Arg[A], a2: Arg[B]) {
def withContext[Res](
f: (A, B, SparkContext) => Res): JobDef[Res]
}
Все дороги ведут в shapeless
shapeless/project/Boilerplate.scala
boilerplate
block"""
|trait Methods {
- def args[${`A..N`}](${`a1..n`}): Args${arity}[${`A..N`}]
- new Args${arity}(${`a1..aN`})
|}
|
|object Methods extends Methods
"""
Что возвращать
trait Job[R] {
def define: JobDef[R]
}
No way
trait Job[A] {
def define: JobDef1[A, R]
def define: JobDef2[A, B, R]
}
Type member
trait Generic[A] {
def foo(a: A)
}
trait Member {
type A
def foo(a: A)
}
Сохранить типы
trait JobDef[R] {
type Args
def args: Args
def run(ctx: Ctx): R
}
HList
class Job1[A, R](
a: Arg[A]
f: (A, SparkContext) => R)
extends JobDef[R] {
type Args = A :: HNil
def args = a :: HNil
}
Последний рывок бойлерплейта
def run(ctx: Context): R = {
val options = args.map(a => a.extract(ctx.params)
if (options.exists(_.isEmpty))
//error
else {
val asTuple = ..
f.tupled(asTuple)
}
}
Это так не работает
import poly._
// а как сюда передать Ctx с параметрами??
object mapExtract extends (ArgDef ~> Option) {
def apply[T](a : ArgDef[T]) = a.extract(?)
}
https://milessabin.com/blog/2012/04/27/shapeless-
polymorphic-function-values-1/
Что получилось
Что получилось
21 - withArgs, ArgN, JobDefN
вот он тот boilrepalte который скрапит shapeless
придется читать сорцы
Понять type-level
type parameters
implicit paramters
type-params + implicit params = type level lang
Могучие типы
trait Foo[A]{
def map(f: A => B): Foo[B]
}
Могучие типы
val a: Int :: HNil = 1 :: HNil
val ab: Int :: String :: HNil = 1 :: "str" :: HNil
implicit ~= match
trait Show[A] {
def show(a: A): String
}
object Show {
implicit val forString: Show[String] = ...
implicit val forInt: Show[Int] = ...
}
implicit ~= match
trait DepFn[A] {
type Out
def apply(a: A): Out
}
object DepFn {
implicit val forString: DepFn[String]
implicit val forInt: DepFn[Int]
}
DepFn
def size(a: Any): Int = a match {
case i: Int = i
case s: String = s
}
trait Size[A] {
type Out
dep apply(a: A): Out
}
object Size[A] {
implicit val forStr: Size[String] = ...
implicit val forInt: Size[Int] = ...
}
def size[A](a: A)(implicit sz: Size[A]): sz.Out =
size(a)
DepFn
object ToHList {
implicit def tuple1[A] = new ToHList[Tuple1[A]] {
type Out = A :: HNil
def apply(a: Tuple1[A]): A :: HNil = a._1 :: HNil
}
}
def foo[A](a: A)(implicit thl: ToHList[A]): thl.Out =
thl(a)
Композиция depfn
def foo[A](a: A)(implicit
thl: ToHList[A],
rev: Reverse[?]
): rev.Out =
rev(thl(a))
Aux
object ToHList {
type Aux[A, Out0] = ToHList[A] {type Out = Out0 }
implicit def tuple1[A]: Aux[Tuple1[A], A :: HNil] =
new ToHList[Tuple1[A]] {
type Out = A :: HNil
def apply(a: Tuple1[A]): A :: HNil = a._1 :: HNil
}
}
Aux
def reverse[A, H <: HList](a: A)(
implicit
thl: ToHList.Aux[A, H],
r: Reverse[H]): r.Out = r(thl(a))
И это все?
The Type Astronaut's Guide to Shapeless
Type Level Programming in Scala step by step
Время поправить
object MyJob extends Job[Int] {
override def define = {
args(arg[String]("path"), arg[Int]("n")).withSparkContext(
(p: String, sc: SparkContext) => {
})
}
}
Scrap your boilerplate
class ArgsN[A ... N ](a1 ... aN)
class JobDefN[A .. N, R](
a1: Arg[A],
...
aN: Arg[N],
f: (A.. N, SparkContext) => R)
extends JobDef[R] {
type RunArgs = A :: N
def args = a :: n
}
Комбинаторы
trait Arg[A] { self =>
def &(b: Arg[B]): Arg[A :: B :: HNil] =
}
Комбинаторы
val a: ArgDef[A] = arg[A]("a")
val b: ArgDef[B] = arg[B]("b")
val c: ArgDef[C] = arg[C]("c")
val ab: ArgDef[A :: B :: HNil] = a & b
val abc: ArgDef[A :: B :: C :: HNil] = a & b & c
Extract
def &(b: Arg[B]): Arg[A :: B :: HNil] = {
new Arg[A :: B :: HNil] {
def extract(ctx: Ctx): Option[A :: B :: HNil] = {
val opt1 = self.extract(p)
val opt2 = b.extract(p)
(opt1, opt2) match {
case (Some(a), Some(b)) => Some(a :: b :: HNil)
}
}
}
}
Больше кейсов
val ab = arg[Int]("a") & arg[Int]("b")
val dc = arg[String]("c") & arg[Int]("d")
val abdc = ab & dc
Adjoin
def &(b: Arg[B])(
implicit
adj: Adjoin[A :: B :: HNil]
): Arg[adj.Out] = {
(opt1, opt2) match {
case (Some(a), Some(b)) => Some(adj(a :: b :: HNil))
}
Контекст это тоже аргумент
val sparkContext = new Arg[SparkContext] {
def extract(ctx: Ctx): = Some(ctx.sparkContext)
}
val args = arg[Int]("n") & sparkContext
FnToProduct
trait Arg[A] { self =>
def apply[F, Int <: HList, Res](f: F)(
implicit
fntp: FnToProduct.Aux[F, In => Res]): JobDef[Res] =
}
val args = arg[Int]("n") & sparkContex
val jobdef = args {(n, sparkContext) => ...}
adapted-args
def foo(ab: (Int, String, Double))
foo((1, "foo", 1.0))
foo(1, "foo", 1.0)
def args[A, H <: HList, ROut, Z](a : A)(
implicit
gen: Generic.Aux[A, H],
r: LeftReducer.Aux[H, reducer.type, ROut],
ev: ROut <:< ArgDef[Z]
)
Репортинг ошибок
implicitNotFound
@implicitNoFound("Not found Foo {A}")
trait Foo[A]
def foo[A](implicit foo: Foo[A])
foo(1) // Not found Foo Int
implicitNotFound
def bar[X, R])(x: X)(implicit x: Aux[X, R], foo: Foo[R])
bar(1) //Not found Foo R
Итог
generic + type level - необходиомость
с этим можно жить

Делаем пользовательское Api на базе Shapeless