【问题标题】:Operations on n-th argument of curried functions in scalascala中柯里化函数的第n个参数的操作
【发布时间】:2016-05-24 01:33:36
【问题描述】:

我正在使用很多柯里化函数,采用类似的参数,但不完全是。出于这个原因,我会发现有一种方法来执行第 n 个参数的转置、应用和组合以及“最终”结果是非常有益的。示例:

val f :X=>Y=>W=>Z
def compose1[A](w :A=>Y) :X=>A=>W=>Z
def transpose1 :X=>W=>Y=>Z
def apply1(y :Y) :X=>W=>Z

对于固定的 n 值,可以通过以下方式轻松完成:

implicit class Apply2[X, Y, Z](private val f :X=>Y=>Z) extends AnyVal {
    def transpose :Y=>X=>Z = { y :Y => x :X => f(x)(y) }
    def provide(y :Y) :X=>Z ={ x :X => f(x)(y) }
    def compose[A](y :A=>Y) : X=>A=>Z = { x :X => a :A => f(x)(y(a)) }
    def apply[A, B]()(implicit ev :Z <:< (A=>B)) :Apply3[X, Y, A, B] = new Apply3[X, Y, A, B]((x :X) => (y :Y) => ev(f(x)(y)))
}

当然,我不欢迎复制粘贴该课程的 22 个版本的想法。对于类型类的最后一个参数,我也可以很容易地做到这一点, 但是对于部分应用非柯里化函数的 scala 下划线表示法同样简洁的解决方案让我望而却步。我觉得应该可以实现以下目标:

val f :A=>B=>C=>D=>E=>F
val c = f()().compose( (x :X) => new C(x)) :A=>B=>X=>D=>E=>F
val t = f()().transpose :A=>B=>D=>C=>E=>F
val s = f()().set(new C()) :A=>B=>D=>E=>F

通过隐式转换为一些Apply,它提供了一个递归的apply() 方法,返回一个嵌套的Apply 实例。

当所有类型都已知时,转换为 HList 并返回的暴力解决方案可行,但 shapless 的依赖有点像一把双刃剑。

【问题讨论】:

  • 您能解释一下“在通用代码中设置陷阱”是什么意思吗?根据我的经验,我总是觉得隐式转换比基于类型类的解决方案更“受困”。
  • 首先是路径依赖类型;尽管它们很强大,但它们也是我沮丧的最大来源(以及过去对编译器错误的困惑)。其次,IntelliJ 不太了解 shapeless。第三,我也没有......这不是图书馆的缺点,我经常使用 HLists,但我有点不情愿在破旧的生产代码周围施展魔法。
  • 删除了关于 shapeless 的评论,因为我没有意识到它是非建设性的批评,而问题在于椅子和键盘之间......

标签: scala functional-programming currying partial-application


【解决方案1】:

好吧,我的心还是很痒,但我终于明白了!不过,我在一段时间内完成的最困难的编程任务。如果有人有改进建议(包括命名、符号和一般语法),我会全力以赴。

/** Represents a partially applied, curried function `F` which is of the form `... X => A`,
  * where X is the type of the first argument after (partial) application.
  * Provides methods for manipulating functions `F` around this argument.
  * @tparam F type of the manipulated function in a curried form (non-empty sequence of single argument lists)
  * @tparam C[G] result of mapping partial result `(X=>A)` of function `F` to `G`.
  * @tparam X type of the argument represented by this instance
  * @tparam A result type of function F partially applied up to and including argument X
  */
abstract class Curry[F, C[G], X, A](private[funny] val f :F) { prev =>
    /** Result of partial application of this function F up to and including parameter `X`. */
    type Applied = A
    /** Replace X=>A with G as the result type of F. */
    type Composed[G] = C[G]
    /** A function which takes argument `W` instead of `X` at this position. */
    type Mapped[W] = Composed[W=>A]


    /** Provide a fixed value for this argument, removing it from the argument list.
      * For example, the result of `Curry{a :Any => b :Byte => c :Char => s"&dollar;a&dollar;b&dollar;c" }().set(1.toByte)`
      * (after inlining) would be a function `{a :Any => c :Char => s"&dollar;a&dollar;{1.toByte}&dollar;c" }`.
      */
    def set(x :X) :Composed[A] = applied[A](_(x))

    /** Change the type of this argument by mapping intended argument type `W` to `X` before applying `f`.
      * For example, given a function `f :F &lt;:&lt; D=>O=>X=>A` and `x :W=>X`, the result is `{d :D => o :O => w :W => f(d)(o)(x(w)) }`.
      */
    def map[W](x :W=>X) :Composed[W=>A] = applied[W=>A]{ r :(X=>A) => (w :W) => r(x(w)) }

    /** Map the result of partial application of this function up to argument `X` (not including).
      * For example, if `F =:= K=>L=>X=>A`, the result is a function `{k :K => l :L => map(f(k)(l)) }`.
      * @param map function taking the result of applying F up until argument `X`.
      * @return resul
      */
    def applied[G](map :((X => A) => G)) :Composed[G]

    /** If the result of this partial application is a function `A &lt;:&lt; Y=>Z`, swap the order of arguments
      * in function `F` from `=>X=>Y=>` to `=>Y=>X=>`.
      */
    def transpose[Y, Z](implicit ev :A<:<(Y=>Z)) :Composed[Y=>X=>Z] = applied[Y=>X=>Z] {
        r :(X=>A) => y :Y => x :X => ev(r(x))(y)
    }


    /** Skip to the next argument, i.e return an instance operating on the result of applying this function to argument `X`. */
    def apply[Y, Z]()(implicit ev :this.type<:<Curry[F, C, X, Y=>Z])  = new NextArg[F, C, X, Y, Z](ev(this))

    /** Skip to the next argument, i.e return an instance operating on the result of applying this function to argument `X`.
      * Same as `apply()`, but forces an implicit conversion from function types which `apply` wouldn't.
      */
    def __[Y, Z](implicit ev :this.type<:<Curry[F, C, X, Y=>Z])  = new NextArg[F, C, X, Y, Z](ev(this))
}


/** Operations on curried functions. */
object Curry {
    type Self[G] = G
    type Compose[C[G], X] = { type L[G] = C[X=>G] }

    /** Extension methods for modifying curried functions at their first argument (and a source for advancing to subsequent arguments. */
    @inline def apply[A, B](f :A=>B) :Arg0[A, B] = new Arg0(f)

    /** Implicit conversion providing extension methods on curried function types. Same as `apply`, but doesn't pollute namespace as much. */
    @inline implicit def ImplicitCurry[A, B](f :A=>B) :Arg0[A, B] = new Arg0(f)

    /** Operations on the first argument of this function. */
    class Arg0[X, Y](x :X=>Y) extends Curry[X=>Y, Self, X, Y](x) {

        def applied[G](map: (X=>Y) => G) :G = map(f)
    }

    class NextArg[F, C[G], X, Y, A](val prev :Curry[F, C, X, Y=>A]) extends Curry[F, (C Compose X)#L, Y, A](prev.f) {

        override def applied[G](map: (Y => A) => G): prev.Composed[X => G] =
            prev.applied[X=>G] { g :(X=>Y=>A) => x :X => map(g(x)) }
    }
}


def f :Byte=>Short=>Int=>Long=>String = ???

import Curry.ImplicitCurry

f.set(1.toByte) :(Short=>Int=>Long=>String)
f.map((_:String).toByte) :(String=>Short=>Int=>Long=>String)
f.__.set(1.toShort) :(Byte=>Int=>Long=>String)
Curry(f)().map((_:String).toShort) : (Byte=>String=>Int=>Long=>String)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-07
    • 2014-11-27
    • 1970-01-01
    • 2017-06-30
    • 2011-08-06
    • 1970-01-01
    相关资源
    最近更新 更多