【问题标题】:Type-parameter-only method overloading in scalascala中的仅类型参数方法重载
【发布时间】:2020-04-15 21:06:33
【问题描述】:

我能否在 scala 中以某种方式实现这样的行为(funcs 具有相同的名称和参数,但仅在返回类型和类型参数上有所不同)?

def a(i:Int): Int //1
def a[T](i: Int): Double //2

a(1) //call 1
a[Any](1) //call 2

UPD 动机:

我正在编写 akka-http 指令auth,它根据权限参数检查用户请求的会话和授权。

  1. 当它只获得一个权限时,它提供刚刚经过身份验证的User

def auth(perm: String): Directive1[User]

  1. 当它通过UUID 获得Tuple 的权限时,它(以某种方式)通过此UUID 获得所需的对象,检查应用于所需对象的权限并提供此对象以及User

def auth[Entity](tup: (String, UUID)): Directive1[(User, Entity)]

  1. 并且当它获得相同的Tuple 但没有类型参数时,它应该检查应用于所需对象的权限但只提供经过身份验证的User

def auth(tup: (String, UUID)): Directive1[User]

这个想法是为所有情况使用相同的指令名称,但似乎无法在语言级别区分 a[T](i: Int): Inta(i: Int): Double(由于类型擦除)。

也许有一些使用implicits 的技巧有助于实现这一目标?

【问题讨论】:

  • 用例是什么?如果T 不代表任何东西,那么它有什么意义。
  • @LuisMiguelMejíaSuárez 请检查问题,我更新了动机
  • 动机似乎是“为所有情况使用相同的指令名称”,但是当它们做不同的事情并以不同的方式调用时,为什么它们具有相同的名称很重要?
  • @Tim 都是做认证和授权的,不同的只是其中一个提供了Entity
  • 返回实体的那个做一些不同于只做身份验证的那个,这是给它一个不同的名字的一个很好的理由。为什么他们必须有相同的名字?这开始感觉像是XY problem

标签: scala generics types overloading akka-http


【解决方案1】:

由于类型擦除,这两种方法将具有相同的签名。

@ class Test {
  def a(i:Int): Int = 1
  def a[T](i: Int): Double = 2.0
  }
defined class Test

@ new Test()
res24: Test = ammonite.$sess.cmd23$Test@7441edeb

@ new Test().a(1)
cmd25.sc:1: ambiguous reference to overloaded definition,
both method a in class Test of type [T](i: Int)Double
and  method a in class Test of type (i: Int)Int
match argument types (Int)
val res25 = new Test().a(1)
                       ^
Compilation Failed

如果您更改了任何内容,例如你如何应用参数,它应该是可行的:

@ class Test {
  def a(i:Int): Int = 1
  def a()(i: Int): Double = 2.0
  }
defined class Test

@ new Test().a(1)
res26: Int = 1

@ new Test().a()(1)
res27: Double = 2.0

所以...取决于您可以更改的数量和内容,是或否。总的来说,这是个坏主意。很少有充分的理由重载方法,在这种情况下重载几乎可以肯定是一种反模式。

【讨论】:

  • 我还检查了两个参数,但使用 implicit 就像:def a[T](i: Int)(implicit numeric: Numeric[T]): Double = ???
  • 擦除后它会有不同的签名,所以它应该可以工作。仍然不知道为什么要首先尝试实现这一目标。
  • 是的,奇怪的是为什么不写一个通用函数,或者给不同的名字))
  • @MateuszKubuszok 请检查问题,我更新了动机
  • @Bob nice catch 带隐式参数,但它似乎不适合 akka-http 指令,因为带有 extractor-func 的代码块应该被视为隐式参数
【解决方案2】:

也许尝试用 ascription 类型消除歧义,像这样

def a(i:Int): Int = 1
def a[T](i: Int): Double = 2

a(1): Int // res0: Int = 1
a[Any](1) // res1: Double = 2.0

【讨论】:

  • 第一种情况导致错误:type mismatch; found: Double, required: Int
  • @mopdobopot;你必须更具体。这段代码由@MarioGalic 提交,通过了我的测试,没有失败,它直接回答了你的原始问题(在模组之前)。
  • 对不起,我的错误,@MarioGalic 代码工作得很好,但不幸的是不适合我的情况
【解决方案3】:

我的回答有点笼统:

def add(a: Int): Int = a + 1
def add[T](a: T)(implicit num: Numeric[T]): Double = {
  num.toDouble(a) + 2.3
}

println(add(5)) // 6
println(add[Int](6)) // 8.3

Scastie上的例子

【讨论】:

  • 带有隐式参数的好主意,但它不适合我的情况,我更新了一个问题,对此感到抱歉
【解决方案4】:

我认为您在这里遇到了separation of concerns 问题。

授权用户和提取数据是不同的事情,所以我会将它们作为两种不同的操作来处理:授权用户的指令和为该用户加载数据的数据库方法。这样做的主要优点是您可以在一次操作中授权用户使用一组端点。

这是可行的简单示例:

def route =
  auth { authUser =>
    (pathEnd | pathSingleSlash) {
      concat(
        get {
          val data = load(authUser, staticUuid)

          ???
        },
        post {
          decodeRequest {
            entity(as[MyRequestBody]) { myRequest =>
              val data = load(authUser, myRequest.uuid)

              ???
            }
          }
        },
      )
    }
  }

如果一直将authUser 传递给方法很尴尬,请将其设置为数据提取方法中的implicit。您也可以将加载操作设为指令,但与auth 不同。

这样可能看起来更冗长,但两年后查看您的代码的人会感谢您使代码易于理解。而那个人很可能就是你。

【讨论】:

  • 我有一个单独的 authService,它会在一个响应中返回一组权限和 User 对象(authService 存储用户)。然后在业务方特殊客户端检查此权限(可能根据此所需的业务对象)。这个客户端自己从数据库中加载对象,它知道哪个对象需要哪个权限。我想要的是将此对象提供给下一个代码,而无需再请求数据库
  • 对,所以auth指令从authService返回Userload方法检查权限并从db加载对象。
猜你喜欢
  • 2011-06-26
  • 2019-01-19
  • 1970-01-01
  • 2021-04-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多