【问题标题】:Scala's type system and the input to FunctionNScala 类型系统和 Function 的输入
【发布时间】:2017-11-09 23:09:11
【问题描述】:

我想知道是否有办法修补 Scala 处理 Function1 和 Function2..N 中明显的不一致。

对于Function1,比如Int => String参数列表 (Int) 与 Int 不同(即使两者是同构的),但编译器会将输入推断为裸Int(参见下面的代码)。

对于Function2..N,说 val f: (String, Int) => String = ??? 编译器不会为输入参数列表推断任何类型。特别是,没有 ParameterList[String, Int] 即使它与 (String, Int) 的元组和任何其他您喜欢围绕 Strings 和 Int 的包装器同构。

我的第一个问题是,是否有这样的原因(或者这是否存在于 scala 待办事项列表中)?即为什么可以将 Function1 解构为输入和输出类型,但不能将 Function2 解构,有人想解决这个问题吗?

是否有任何变通方法。具体来说,在下面的代码中,有没有办法让 invoke2 工作?

package net.jtownson.swakka

import org.scalatest.FlatSpec
import org.scalatest.Matchers._
import shapeless.ops.function._
import shapeless.{HList, HNil, _}

class TypeclassOfFunctionTypeSpec extends FlatSpec {

  // Here, we know the return type of F is constrained to be O
  // (because that's how the shapeless FnToProduct typeclass works)
  def invoke1[F, I <: HList, O](f: F, i: I)
                               (implicit ftp: FnToProduct.Aux[F, I => O]): O = ftp(f)(i)

  // So let's try to express that by extracting the input type of F as FI
  def invoke2[FI, I <: HList, O](f: FI => O, i: I)
                                (implicit ftp: FnToProduct.Aux[FI => O, I => O]): O = ftp(f)(i)

  "Invoke" should "work for a Function1" in {

    // Here's our function (Int) => String
    val f: (Int) => String = (i) => s"I got $i"

    val l = 1 :: HNil

    // this works
    val r1: String = invoke1(f, l)

    // So does this. (With evidence that the compiler sees the function parameter list (Int) as just Int
    val r2: String = invoke2[Int, Int::HNil, String](f, l)

    r1 shouldBe "I got 1"
    r2 shouldBe "I got 1"
  }

  "Invoke" should "work for a Function2" in {

    // Here's our function (String, Int) => String
    val f: (String, Int) => String = (s, i) => s"I got $s and $i"

    val l = "s" :: 1 :: HNil

    // this works
    val r1: String = invoke1(f, l)

    // But this does not compile. There is no expansion for the type of FI
    // (String, Int) != the function Parameter list (String, Int)
    val r2: String = invoke2(f, l) 
    /*
    Error:(...) type mismatch;
    found   : (String, Int) => String
      required: ? => String
        val r1: String = invoke1(f, l)
    */

    r1 shouldBe "I got s and 1"
    r2 shouldBe "I got s and 1"
  }
}

【问题讨论】:

  • 您知道Function2#tupledFunction2#curried 方法吗?
  • 是的,我是 :-) 而且 Function1 上不存在元组。大概这是因为表达 Function1 的 input 类型(作为裸类型)的能力意味着不需要元组。可怜的旧 Function2..N 缺少这个。从这个意义上说,tupled 感觉像是一种解决方法。不过,我想我想知道的是,Function1 和 2 在这方面存在差异是否有充分的理由?

标签: scala shapeless type-systems


【解决方案1】:

Int =&gt; StringFunction1[Int, String] 的语法糖,(String, Int) =&gt; StringFunction2[String, Int, String] 的语法糖,((String, Int)) =&gt; StringFunction1[(String, Int), String] 又名Function1[Tuple2[String, Int], String] 的语法糖。

如果你定义了隐式转换,你可以帮助 Shapeless 解析 FnToProduct 实例

implicit def tupledFnToProduct[FI1, FI2, O, Out0](implicit
  ftp: FnToProduct.Aux[Function2[FI1, FI2, O], Out0]
  ): FnToProduct.Aux[Function1[(FI1, FI2), O], Out0] =
  new FnToProduct[Function1[(FI1, FI2), O]] {
    override type Out = Out0
    override def apply(f: Function1[(FI1, FI2), O]) = ftp((x, y) => f(x, y))
  }

然后你可以用.tupled打电话给invoke2

val f: (String, Int) => String = (s, i) => s"I got $s and $i"

val l = "s" :: 1 :: HNil

val r2: String = invoke2(f.tupled, l)

r2 == "I got s and 1" //true

【讨论】:

    猜你喜欢
    • 2015-06-08
    • 1970-01-01
    • 1970-01-01
    • 2011-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-17
    • 1970-01-01
    相关资源
    最近更新 更多