任意函数 - 根据输入生成返回类型

时间:2015-09-14 13:26:42

标签: scala shapeless type-level-computation

鉴于这些课程:

class Step
case class Publish(p: String) extends Step
case class Receive(p: String) extends Step

class Data
case class Input(p: Int) extends Data
case class Output(p: Int) extends Data

type P = Publish
type R = Receive
type I = Input
type O = Output

我希望method / trait / classStep* / Publish序列中获取Receive个参数并返回一种在Data / Input序列中获取Output个参数的方法。

例如,对于:

val flow = Flow {"flow" =>
    (P("1"), R("2"), P("3"), R("4"), R("5"), P("6"), R("7"))
}

我应该可以接听电话:

flow(I(1), O(2), I(3), O(4), O(5), I(6), O(7))

我最接近的是将Flow定义为对象:

object Flow {
    def apply[T](f: String => T)(implicit val s: String): T => Unit = f(s) => ()
}

它为上面的例子提供了flow: (P, R, P, R, R, P, R) => Unit

的签名

如何映射P -> IR -> O以便使用输入和输出对flow进行调用,并确保在编译时进行类型检查?

在寻找解决方案时,我偶然发现了shapelessscalaz作为我认为可能对我有用的框架,但我无法弄明白。

this answer之后,我将Flow改写为:

object Flow {
    def apply[T <: HList, S <: HList](t: T)
        (implicit mapper : Mapper[IOFlow.type, T]): S => Unit = {
        val s = t.map(IOFlow)
        s => ()
    }
}

object IOFlow extends Poly1 {
    implicit val in = at[P]{ i => makeI(i) }
    implicit val out = at[R]{ r => makeO(r) }

    def makeI(p: P) = Input // so the return type is Input.type
    def makeO(p: R) = Output // and Output.type
}

object Main extends App {

    val initialList = HList(P("1"), R("2"), P("3"), R("4"), R("5"), P("6"), R("7"))
    val flow = Flow(initialList)
}

我收到implicit mapper的错误,实际上是t.map(IOFLow)收到的:

Error: could not find implicit value for parameter mapper: shapeless.ops.hlist.Mapper[com.tasegula.scala.shapeless.IOFlow.type,this.Repr]
val flow = Flow(hlist)
               ^

所以:

  1. 如何提供隐式映射器?
  2. 有没有办法将Flow#apply方法声明更改为apply(t: Step*),因此调用将是Flow(P("1"), R("2"), P("3"), R("4"), R("5"), P("6"), R("7"))

2 个答案:

答案 0 :(得分:2)

你怎么能这么容易地做到这一点?我会看一下Shapeless,它可以通过定义Poly来实际完成你想做的一切:

object IOFlow extends Poly1{
  implicit val in = at[P]{ i => makeI(i) }
  implicit val out = at[R]{ r => makeO(r) }

  def makeI(p: P): I
}

通过将您的调用转换为HList,您将能够映射:

val input: I :: O :: I :: O :: I :: O = prprpr.map(IOFlow)

答案 1 :(得分:0)

经过多次试验和错误,终于得到了解决方案: 为Data类型创建了一个包装器,所以:

Publish returns a Wrapper1[Input]
Receive returns a Wrapper1[Output]

另外,因为我有一系列步骤,所以我为每个可能的参数创建了Wrapper

case class Wrapper1[T1](s1: String)
case class Wrapper2[T1, T2](s1: String, s2: String)
...

此包装器中的每一个都有一个方法,它将其与Wrapper1[S]结合并返回Wrapper(n+1)[T1..Tn, S]。 此外,每个都有一个start方法,它接受给定类型的n个参数。

case class Wrapper1[T1](s1: String) {
    def +[S](step: W1[S]): W2[T1, S] = W2(s1, step.s1)

    def start(p1: T1) = println("PARAMS: " + List(p1))
}

case class Wrapper2[T1, T2](s1: String, s2: String) {
    def +[S](step: W1[S]): W3[T1, T2, S] = W2(s1, s2, step.s1)

    def start(p1: T1, p2: T2) = println("PARAMS: " + List(p1, p2))
}

现在,IOFlow成为每个apply Wrapper的对象:

object IOFlow {
    def apply[T1](flow: W1[T1]): (T1) => Unit = flow.start
    def apply[T1, T2](flow: W2[T1, T2]): (T1, T2) => Unit = flow.start
    ...
}

Main app:

object Main extends App {
    val flow = IOFLow(P("1") + R("2") + P("3") + R("4") + R("5") + P("6") + R("7"))
    flow(I(1), O(2), I(3), O(4), O(5), I(6), O(7))
}

所以,在窗帘后面,会发生这种情况:

P("1") + R("2") + P("3") + R("4") + R("5") + P("6") + R("7") =>
W1[I]  + W1[O]  + W1[I]  + W2[O]  + W1[O]  + W1[I]  + W1[O]

然后每个+采取行动:

W1[I]  + W1[O]  + W1[I]  + W2[O]  + W1[O]  + W1[I]  + W1[O] =
W2[I, O]        + W1[I]  + W2[O]  + W1[O]  + W1[I]  + W1[O] = 
W3[I, O, I]              + W2[O]  + W1[O]  + W1[I]  + W1[O] = 
W4[I, O, I, O]                    + W1[O]  + W1[I]  + W1[O] = 
W5[I, O, I, O, O]                          + W1[I]  + W1[O] = 
W6[I, O, I, O, O, I]                                + W1[O] = 
W7[I, O, I, O, O, I, O]

所以电话实际上就是:

val flow = IOFlow(W7[I, O, I, O, O, I, O]("1", "2", "3", "4", "5", "6", "7"))

这意味着它将被调用:

apply[T1, T2, T3, T4, T5, T6, T7](flow: W7[T1, T2, T3, T4, T5, T6, T7]): (T1, T2, T3, T4, T5, T6, T7) => Unit = flow.start

这意味着flow将是一个需要7个参数的函数:(T1, T2, T3, T4, T5, T6, T7)并返回Unit。此函数的行为在Wrapper7#start方法中定义。