Scala +无形抽象的咖喱功能

时间:2017-10-30 03:24:28

标签: scala shapeless currying

我正在试图弄清楚如何在curried函数上进行抽象。 我可以通过以下方式抽象出未经验证的函数:

def liftAU[F, P <: Product, L <: HList, R, A[_]](f: F)
(implicit
 fp: FnToProduct.Aux[F, L => R],
 gen: Generic.Aux[P, L],
 ap: Applicative[A]
): A[P] => A[R] = p => p.map(gen.to).map(f.toProduct)

这将采用类似(Int,Int)=&gt;的函数。 Int并将其转换为类似Option [(Int,Int)] =&gt;的内容。选项[INT]。它适用于任何功能。

我想创建一个curried版本,它将采用类似Int =&gt;的函数。 Int =&gt; Int并将其转换为Option [Int] =&gt;选项[Int] =&gt;选项[INT]。

它也应该适用于任何咖喱功能。

由于FnToProduct仅适用于第一个参数列表,因此在这里没有用,我也尝试在类型级别编写一些递归定义,但是我在定义类型时遇到了问题。

不确定它是否可行,但很想知道其他人是否尝试过这样的事情。

2 个答案:

答案 0 :(得分:3)

除非我更改其中一个对象中的实例名称,否则Dmytro的答案实际上并不适用于我,即使这样,它也不适用于像Int => Int => Int => Int这样的函数,而且我觉得使用Poly值非常烦人,所以我没有调试上一个答案,而只是自己编写。

你可以使用100%无Shapeless类型来实际编写这个操作:

import cats.Applicative

trait LiftCurried[F[_], I, O] {
  type Out
  def apply(f: F[I => O]): F[I] => Out
}

object LiftCurried extends LowPriorityLiftCurried {
  implicit def liftCurried1[F[_]: Applicative, I, I2, O2](implicit
    lc: LiftCurried[F, I2, O2]
  ): Aux[F, I, I2 => O2, F[I2] => lc.Out] = new LiftCurried[F, I, I2 => O2] {
    type Out = F[I2] => lc.Out
    def apply(f: F[I => I2 => O2]): F[I] => F[I2] => lc.Out =
      (Applicative[F].ap(f) _).andThen(lc(_))
  }
}

trait LowPriorityLiftCurried {
  type Aux[F[_], I, O, Out0] = LiftCurried[F, I, O] { type Out = Out0 }

  implicit def liftCurried0[F[_]: Applicative, I, O]: Aux[F, I, O, F[O]] =
    new LiftCurried[F, I, O] {
      type Out = F[O]
      def apply(f: F[I => O]): F[I] => F[O] = Applicative[F].ap(f) _
    }
}

它可能会使它更清洁,但我发现它是合理的可读性。

你可能希望得到像这样具体的东西:

def liftCurriedIntoOption[I, O](f: I => O)(implicit
  lc: LiftCurried[Option, I, O]
): Option[I] => lc.Out = lc(Some(f))

然后我们可以证明它适用于这样的一些函数:

val f: Int => Int => Int = x => y => x + y
val g: Int => Int => Int => Int = x => y => z => x + y * z
val h: Int => Int => Int => String => String = x => y => z => _ * (x + y * z)

然后:

scala> import cats.instances.option._
import cats.instances.option._

scala> val ff = liftCurriedIntoOption(f)
ff: Option[Int] => (Option[Int] => Option[Int]) = scala.Function1$$Lambda$1744/350671260@73d06630

scala> val gg = liftCurriedIntoOption(g)
gg: Option[Int] => (Option[Int] => (Option[Int] => Option[Int])) = scala.Function1$$Lambda$1744/350671260@2bb9b82c

scala> val hh = liftCurriedIntoOption(h)
hh: Option[Int] => (Option[Int] => (Option[Int] => (Option[String] => Option[String]))) = scala.Function1$$Lambda$1744/350671260@45eec9c6

我们还可以多次应用它只是为了它的地狱:

scala> val hhhh = liftCurriedIntoOption(liftCurriedIntoOption(hh))
hhh: Option[Option[Option[Int]]] => (Option[Option[Option[Int]]] => (Option[Option[Option[Int]]] => (Option[Option[Option[String]]] => Option[Option[Option[String]]]))) = scala.Function1$$Lambda$1744/350671260@592593bd

所以类型看起来没问题,而且价值......

scala> ff(Some(1))(Some(2))
res0: Option[Int] = Some(3)

scala> ff(Some(1))(None)
res1: Option[Int] = None

scala> hh(Some(1))(None)(None)(None)
res2: Option[String] = None

scala> hh(Some(1))(Some(2))(Some(3))(Some("a"))
res3: Option[String] = Some(aaaaaaa)

......我认为这是你的目标。

答案 1 :(得分:1)

您可以定义递归Poly

  object constNone extends Poly1 {
    implicit def zeroCase[In]: Case.Aux[In, Option[Int]] = at(_ => None)

    implicit def succCase[In, In1, Out](implicit 
      cse: Case.Aux[In, Out]): Case.Aux[In1, In => Out] = at(_ => cse(_))
  }

  object transform extends Poly1 {
    implicit def zeroCase: Case.Aux[Int, Option[Int]] = at(Some(_))

    implicit def succCase[In, Out](implicit 
      cse: Case.Aux[In, Out], 
      noneCase: constNone.Case.Aux[In, Out]
    ): Case.Aux[Int => In, Option[Int] => Out] =
      at(f => {
        case Some(n) => cse(f(n))
        case None => noneCase(f(0))
      })
  }

  (transform((x: Int) => (y: Int) => x + y) _)(Some(1))(Some(2)) //Some(3)
  (transform((x: Int) => (y: Int) => x + y) _)(Some(1))(None) //None
  (transform((x: Int) => (y: Int) => x + y) _)(None)(Some(2)) //None