스칼라 컴파일러가 왜 기본 인수로 오버로드 된 메소드를 허용하지 않습니까?


148

그러한 메소드 오버로딩이 모호해질 수있는 유효한 경우가있을 수 있지만, 컴파일러는 왜 컴파일 타임이나 런타임에 모호하지 않은 코드를 허용하지 않습니까?

예:

// This fails:
def foo(a: String)(b: Int = 42) = a + b
def foo(a: Int)   (b: Int = 42) = a + b

// This fails, too. Even if there is no position in the argument list,
// where the types are the same.
def foo(a: Int)   (b: Int = 42) = a + b
def foo(a: String)(b: String = "Foo") = a + b

// This is OK:
def foo(a: String)(b: Int) = a + b
def foo(a: Int)   (b: Int = 42) = a + b    

// Even this is OK.
def foo(a: Int)(b: Int) = a + b
def foo(a: Int)(b: String = "Foo") = a + b

val bar = foo(42)_ // This complains obviously ...

이러한 제한을 조금 풀 수없는 이유가 있습니까?

특히 과부하가 걸린 Java 코드를 스칼라 기본 인수로 변환하는 것은 매우 중요하며 스펙 / 컴파일러가 임의의 제한을 부과하는 하나의 스칼라 메소드로 많은 Java 메소드를 대체 한 후에는 찾기가 좋지 않습니다.


18
"임의 제한" :-)
KajMagnus

1
형식 인수를 사용하여 문제를 해결할 수있는 것 같습니다. 컴파일 :object Test { def a[A](b: Int, c: Int, d: Int = 7): Unit = {}; def a[A](a:String, b: String = ""): Unit = {}; a(2,3,4); a("a");}
user1609012

@ user1609012 : 당신의 트릭이 나를 위해 작동하지 않았습니다. Scala 2.12.0 및 Scala 2.11.8을 사용하여 시도했습니다.
Landlocked Surfer

4
IMHO 이것은 스칼라에서 가장 강한 고통 중 하나입니다. 유연한 API를 제공하려고 할 때, 특히 컴패니언 객체의 apply ()를 오버로드 할 때 종종이 문제가 발생합니다. 나는 약간 코 틀린를 통해 스칼라를 선호하지만, 코 틀린에 당신은 ... 과부하 이런 종류의 작업을 수행 할 수 있습니다
입방 상추

답변:


113

Lukas Rytz ( 여기 부터 ) 를 인용하고 싶습니다 .

그 이유는 기본 인수를 반환하는 생성 된 메서드에 대한 결정적 명명 체계를 원했기 때문입니다. 당신이 쓰는 경우

def f(a: Int = 1)

컴파일러는

def f$default$1 = 1

동일한 매개 변수 위치에 기본값으로 두 개의 과부하가있는 경우 다른 이름 지정 체계가 필요합니다. 그러나 여러 컴파일러 실행에서 생성 된 바이트 코드를 안정적으로 유지하려고합니다.

향후 스칼라 버전에 대한 해결책은 기본이 아닌 인수의 유형 이름 (과도한 방법으로 오버로드 된 버전을 명확하게하는 메소드 시작시)을 이름 지정 스키마에 통합하는 것입니다 (예 :이 경우).

def foo(a: String)(b: Int = 42) = a + b
def foo(a: Int)   (b: Int = 42) = a + b

그것은 다음과 같을 것입니다 :

def foo$String$default$2 = 42
def foo$Int$default$2 = 42

누군가 SIP 제안서를 기꺼이 작성 하시겠습니까?


2
나는 당신의 제안이 여기에 많은 의미가 있다고 생각하며, 그것을 지정 / 구현하는 데 너무 복잡한 것이 무엇인지 알지 못합니다. 기본적으로 매개 변수 유형은 함수 ID의 일부입니다. 컴파일러는 현재 foo (String) 및 foo (Int)로 무엇을합니까 (즉, 기본값이없는 오버로드 된 메소드)?
Mark

Java에서 스칼라 메소드에 액세스 할 때 강제 헝가리어 표기법이 효과적으로 도입되지 않습니까? 인터페이스가 매우 취약하기 때문에 유형 매개 변수가 변경 될 때 사용자가주의를 기울여야합니다.
blast_hardcheese 2014

또한 복잡한 유형은 어떻습니까? A with B예를 들어?
blast_hardcheese 2014

66

오버로드 해상도와 기본 인수의 상호 작용에 대한 읽기 쉽고 정확한 사양을 얻는 것은 매우 어렵습니다. 물론 여기에 제시된 사례와 같은 많은 개별 사례의 경우 어떤 일이 발생해야하는지 쉽게 말할 수 있습니다. 그러나 충분하지 않습니다. 가능한 모든 코너 케이스를 결정하는 사양이 필요합니다. 오버로드 해상도는 이미 지정하기가 매우 어렵습니다. 믹스에 기본 인수를 추가하면 여전히 어려워집니다. 그래서 우리는 둘을 분리하기로 결정했습니다.


4
답변 주셔서 감사합니다. 아마도 나를 혼란스럽게 한 것은 기본적으로 다른 곳에서는 컴파일러가 실제로 모호성이있는 경우에만 불평한다는 것입니다. 그러나 여기서 모호성이 발생할 수있는 유사한 사례가있을 수 있으므로 컴파일러가 불평합니다. 따라서 첫 번째 경우 컴파일러는 입증 된 문제가있는 경우에만 불만을 제기하지만 두 번째 경우에는 컴파일러 동작이 훨씬 덜 정확하고 "겉보기에 유효한"코드에 대한 오류를 트리거합니다. 가장 놀랍지 않은 원리로 이것을 보았을 때, 이것은 약간 불행합니다.
soc

2
"읽고 정확한 사양을 얻는 것이 매우 어려울 것입니다 ...] 누군가가 좋은 사양 및 / 또는 구현으로 한 단계 올라가면 현재 상황이 개선 될 수있는 실제 기회가 있다는 것을 의미합니까? 현재 상황 imho는 명명 된 / 기본 매개 변수의 사용성을 상당히 제한합니다.
soc

사양 변경을 제안하는 프로세스가 있습니다. scala-lang.org/node/233
James Iry

2
나는 몇 가지 의견이 스칼라에 대한이 (링크 된 답변을 아래에 내 의견을 참조) 눈살을 찌푸리게하고 이등 시민 과부하 만들기. 스칼라에서 오버로드를 의도적으로 약화시키는 경우 타이핑을 이름으로 바꾸는 것입니다. IMO는 회귀적인 방향입니다.
쉘비 무어 III

10
파이썬이 할 수 있다면, 스칼라가 할 수없는 좋은 이유는 없습니다. 복잡성에 대한 논쟁은 좋은 것입니다.이 기능을 구현하면 사용자 관점에서 스케일이 덜 복잡해집니다. 다른 답변을 읽으면 사용자의 관점에서 존재해서는 안되는 문제를 해결하기 위해 매우 복잡한 것을 발명하는 사람들을 볼 수 있습니다.
Richard Gomes

12

귀하의 질문에 대답 할 수 없지만 다음과 같은 해결 방법이 있습니다.

implicit def left2Either[A,B](a:A):Either[A,B] = Left(a)
implicit def right2Either[A,B](b:B):Either[A,B] = Right(b)

def foo(a: Either[Int, String], b: Int = 42) = a match {
  case Left(i) => i + b
  case Right(s) => s + b
}

하나의 arg에서만 다른 두 개의 매우 긴 arg 목록이 있으면 문제가 될 수 있습니다 ...


1
글쎄, 기본 인수를 사용하여 코드를보다 간결하고 읽기 쉽게 만들려고했습니다 ... 실제로 대체 유형을 허용되는 유형으로 변환 한 경우에는 클래스에 암시 적 변환을 추가했습니다. 그것은 못생긴 느낌입니다. 그리고 기본 args를 사용하는 접근 방식은 효과가 있습니다!
soc

그들은 모든 용도에 적용하기 때문에 당신은 이러한 변환에주의해야 Either만을위한하지 foo때마다 이런 식으로, - Either[A, B]값을 요청, 모두 AB사용할 수 있습니다. foo이 방향으로 가고 싶다면 기본 인수 (예 : 여기) 가있는 함수에서만 허용되는 유형을 대신 정의해야합니다 . 물론 이것이 편리한 솔루션인지 여부는 훨씬 명확하지 않습니다.
Blaisorblade

9

나를 위해 일한 것은 오버로드 방법을 재정의하는 것입니다 (Java 스타일).

def foo(a: Int, b: Int) = a + b
def foo(a: Int, b: String) = a + b
def foo(a: Int) = a + "42"
def foo(a: String) = a + "42"

이를 통해 현재 매개 변수에 따라 원하는 해상도를 컴파일러에 보장 할 수 있습니다.


3

@ 랜디 답변의 일반화는 다음과 같습니다.

당신이 정말로 원하는 것 :

def pretty(tree: Tree, showFields: Boolean = false): String = // ...
def pretty(tree: List[Tree], showFields: Boolean = false): String = // ...
def pretty(tree: Option[Tree], showFields: Boolean = false): String = // ...

해결 방법

def pretty(input: CanPretty, showFields: Boolean = false): String = {
  input match {
    case TreeCanPretty(tree)       => prettyTree(tree, showFields)
    case ListTreeCanPretty(tree)   => prettyList(tree, showFields)
    case OptionTreeCanPretty(tree) => prettyOption(tree, showFields)
  }
}

sealed trait CanPretty
case class TreeCanPretty(tree: Tree) extends CanPretty
case class ListTreeCanPretty(tree: List[Tree]) extends CanPretty
case class OptionTreeCanPretty(tree: Option[Tree]) extends CanPretty

import scala.language.implicitConversions
implicit def treeCanPretty(tree: Tree): CanPretty = TreeCanPretty(tree)
implicit def listTreeCanPretty(tree: List[Tree]): CanPretty = ListTreeCanPretty(tree)
implicit def optionTreeCanPretty(tree: Option[Tree]): CanPretty = OptionTreeCanPretty(tree)

private def prettyTree(tree: Tree, showFields: Boolean): String = "fun ..."
private def prettyList(tree: List[Tree], showFields: Boolean): String = "fun ..."
private def prettyOption(tree: Option[Tree], showFields: Boolean): String = "fun ..."

1

가능한 시나리오 중 하나는


  def foo(a: Int)(b: Int = 10)(c: String = "10") = a + b + c
  def foo(a: Int)(b: String = "10")(c: Int = 10) = a + b + c

컴파일러는 어느 것을 호출할지 혼동 될 것입니다. 다른 가능한 위험을 방지하기 위해 컴파일러는 최대 하나의 오버로드 된 메소드가 기본 인수를 갖도록 허용합니다.

그냥 내 추측 :-)


0

내 이해는 기본 인수 값으로 컴파일 된 클래스에 이름 충돌이있을 수 있다는 것입니다. 여러 줄에서 언급 된이 줄을 따라 뭔가를 보았습니다.

명명 된 인수 사양은 다음과 같습니다. http://www.scala-lang.org/sites/default/files/sids/rytz/Mon,%202009-11-09,%2017:29/named-args.pdf

상태는 다음과 같습니다.

 Overloading If there are multiple overloaded alternatives of a method, at most one is
 allowed to specify default arguments.

따라서 당분간은 작동하지 않습니다.

Java에서 할 수있는 것과 같은 작업을 수행 할 수 있습니다. 예 :

def foo(a: String)(b: Int) =  a + (if (b > 0) b else 42)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.