Kadai CMDOPTS
Twitter: @possiblywrong
Bitbucket: dconlon
Let's see where types can take us...
> ./prog --name Robert Paulson --age 65
> ./prog --host www.atlassian.com -p 8080
import kadai.cmdopts._
object Example extends App {
case class Name(fst: String, sur: String)
object CFG extends CmdOpts(args) {
// name has type Option[Name]
lazy val name = opt("--name", (x: String, y:String) => Name(x, y))
}
println(CFG.name)
}
implicit resolution
Compiler give me something that has this shape/type!
HList / Heterogeneous List
The progeny of TupleX and List[_].
abstract class CmdOpts[T](rawdata: Seq[T]) {
def opt[H <: HList, N <: Nat, F, R](t: T, f: F)(
implicit hlister: FnHListerAux[F, H => R],
length: LengthAux[H, N],
toHList: FromTraversable[H],
arity: ToInt[N]): Option[R] =
for {
ts <- if (arity() > 0) tailfind(t)
else rawdata.find { _ == t }.map { _ => Nil }
hl <- toHList(ts.take(arity()))
op <- nonFatalCatch.opt { hlister(f)(hl) }
} yield op
private def tailfind(t: T): Option[Seq[T]] =
rawdata.dropWhile(_ != t).tailOption
}
hlister: FnHListerAux[F, H => R]
Takes a function and returns a new function that can be applied to an HList rather than an argument tuple.
trait Function1[-T1, +R] extends AnyRef
length: LengthAux[H, N]
Simply a witness for the length of the HList.
toHList: FromTraversable[H]
A function to convert a traversable into an HList.
toHList: Option[H]
(x:String, y:String) => Name(x,y)
Arg HList type, String :: String :: HNil
arity: ToInt[N]
A function that returns a single integer.
Extract type information and use it at runtime.
abstract class CmdOpts[T](rawdata: Seq[T]) {
def opt[H <: HList, N <: Nat, F, R](t: T, f: F)(
implicit hlister: FnHListerAux[F, H => R],
length: LengthAux[H, N],
toHList: FromTraversable[H],
arity: ToInt[N]): Option[R] =
for {
ts <- if (arity() > 0) tailfind(t)
else rawdata.find { _ == t }.map { _ => Nil }
hl <- toHList(ts.take(arity()))
op <- nonFatalCatch.opt { hlister(f)(hl) }
} yield op
private def tailfind(t: T): Option[Seq[T]] =
rawdata.dropWhile(_ != t).tailOption
}
FnHListerAux
implicit def fnHLister1[A, Res] =
new FnHListerAux[(A) => Res, (A :: HNil) => Res] {
def apply(fn : (A) => Res) = (l : A :: HNil) => l match { case a :: HNil => fn(a) }
}
FnHListerAux
implicit def fnHLister1[A, Res] =
new FnHListerAux[(A) => Res, (A :: HNil) => Res] {
def apply(fn : (A) => Res) = (l : A :: HNil) => l match { case a :: HNil => fn(a) }
}
...yes there is an fnHLister22
LengthAux
trait LengthAux[-L <: HList, N <: Nat] {
def apply() : N
}
object LengthAux {
import Nat._
implicit def hnilLength = new LengthAux[HNil, _0] {
def apply() = _0
}
implicit def hlistLength[H, T <: HList, N <: Nat](
implicit lt : LengthAux[T, N], sn : Succ[N]) =
new LengthAux[H :: T, Succ[N]] {
def apply() = sn
}
}
Example: Name (String :: String :: HNil)
Target, LengthAux[String :: String :: HNil, N]
got LengthAux[String :: String :: HNil, Succ[N1]]
need LengthAux[String :: HNil, N1] & Succ[N1]
got LengthAux[String :: HNil,Succ[N2]]
need LengthAux[HNil,N2] & Succ[N2]
Base case
LengthAux[HNil,N2] implies N2 is _0
implicit def hnilLength = new LengthAux[HNil, _0] {
def apply() = _0
}
got LengthAux[HNil,_0] & Succ[_0]
implies LengthAux[String :: HNil,Succ[_0]]
got LengthAux[String :: HNil, Succ[_0]] & Succ[Succ[_0]]
implies LengthAux[String :: String :: HNil, Succ[Succ[_0]]
N == Succ[Succ[_0]]
Succ[Succ[_0]] == _2
Satisfied & nearly proved...
trait LengthAux[-L <: HList, N <: Nat] {
def apply() : N
}
object LengthAux {
import Nat._
implicit def hnilLength = new LengthAux[HNil, _0] {
def apply() = _0
}
implicit def hlistLength[H, T <: HList, N <: Nat](
implicit lt : LengthAux[T, N], sn : Succ[N]) =
new LengthAux[H :: T, Succ[N]] {
def apply() = sn
}
}
toHList/FromTraversable & arity/ToInt
are derived similarly to FnHListerAux
Done!
abstract class CmdOpts[T](rawdata: Seq[T]) {
def opt[H <: HList, N <: Nat, F, R](t: T, f: F)(
implicit hlister: FnHListerAux[F, H => R],
length: LengthAux[H, N],
toHList: FromTraversable[H],
arity: ToInt[N]): Option[R] =
for {
ts <- if (arity() > 0) tailfind(t)
else rawdata.find { _ == t }.map { _ => Nil }
hl <- toHList(ts.take(arity()))
op <- nonFatalCatch.opt { hlister(f)(hl) }
} yield op
private def tailfind(t: T): Option[Seq[T]] =
rawdata.dropWhile(_ != t).tailOption
}