반환 유형이 오버로드되지 않는 이유는 무엇입니까? (적어도 일반적으로 사용되는 언어로)


9

모든 프로그래밍 언어에 대해 잘 모르겠지만 일반적으로 반환 유형을 고려하여 메서드를 오버로드 할 가능성이 있습니다 (인수가 동일한 숫자 및 유형이라고 가정).

나는 이와 같은 것을 의미한다 :

 int method1 (int num)
 {

 }
 long method1 (int num)
 {

 }

그것은 프로그래밍에 큰 문제가 아니라 어떤 경우에는 그것을 환영했을 것입니다.

이러한 언어가 어떤 메소드가 호출되는지 구별 할 수있는 방법없이이를 지원할 수있는 방법은 없지만, 그 구문은 [int] method1 (num) 또는 [long] method1 (num)과 같이 간단 할 수 있습니다. 그렇게하면 컴파일러는 어떤 것이 호출 될지를 알 것입니다.

컴파일러의 작동 방식에 대해서는 잘 모르지만 그렇게하기 어려운 것처럼 보이지 않으므로 왜 그런 것이 일반적으로 구현되지 않는지 궁금합니다.

그와 같은 것이 지원되지 않는 이유는 무엇입니까?


어쩌면 두 반환 유형 사이에 암시 적 변환 (예 : 클래스 Foo및) 이없는 예를 사용하면 질문이 더 나을 것 Bar입니다.

그렇다면 왜 그러한 기능이 유용할까요?
James Youngman

3
@JamesYoungman : 예를 들어 문자열을 다른 유형으로 구문 분석하기 위해 int read (String s), float read (String s) 등의 메소드를 사용할 수 있습니다. 오버로드 된 메소드 변형은 해당 유형에 대한 구문 분석을 수행합니다.
Giorgio

그건 그렇고 정적으로 형식이 지정된 언어의 경우에만 문제입니다. Javascript 또는 Python과 같이 동적으로 유형이 지정된 언어에서는 여러 개의 반환 유형을 갖는 것이 일반적입니다.
로봇 고트

1
@StevenBurnap, 음. 예를 들어 JavaScript를 사용하면 함수 오버로드를 전혀 수행 할 수 없습니다. 따라서 이것은 실제로 함수 이름 오버로드를 지원하는 언어의 문제입니다.
David Arno

답변:


19

유형 검사가 복잡합니다.

인수 유형을 기반으로 오버로드 만 허용하고 초 기자에서 변수 유형을 추론 할 수있는 경우 모든 유형 정보는 한 방향으로 구문 트리로 흐릅니다.

var x = f();

given      f   : () -> int  [upward]
given      ()  : ()         [upward]
therefore  f() : int        [upward]
therefore  x   : int        [upward]

유형 정보를 사용에서 변수를 추론하는 것과 같이 유형 정보가 양방향 으로 이동하도록 허용하는 경우 유형 을 결정하려면 구속 조건 솔버 (예 : Hindley-Milner 유형 시스템의 경우 알고리즘 W)가 필요합니다.

var x = parse("123");
print_int(x);

given      parse        : string -> T  [upward]
given      "123"        : string       [upward]
therefore  parse("123") : ∃T           [upward]
therefore  x            : ∃T           [upward]
given      print_int    : int -> ()    [upward]
therefore  print_int(x) : ()           [upward]
therefore  int -> ()    = ∃T -> ()     [downward]
therefore  ∃T           = int          [downward]
therefore  x            : int          [downward]

여기서 우리는 타입을 x해결되지 않은 타입 변수로 남겨 두어야했습니다 ∃T. 여기서 우리가 아는 것은 파싱 가능하다는 것입니다. 나중에 x구체적인 유형으로 사용될 때 제약 조건을 해결하고이를 결정하기 위해 충분한 정보가 있습니까?이 ∃T = int정보 는 유형 정보를 호출 표현식에서 구문 트리 전파합니다 x.

의 유형을 결정하지 못한 x경우이 코드도 오버로드되거나 호출자가 유형을 결정하거나 모호성에 대한 오류를보고해야합니다.

이를 통해 언어 디자이너는 다음과 같은 결론을 내릴 수 있습니다.

  • 구현에 복잡성을 추가합니다.

  • 병리학 적 경우 기하 급수적으로 유형 검사가 느려집니다.

  • 좋은 오류 메시지를 생성하기가 더 어렵습니다.

  • 현상 유지와 너무 다릅니다.

  • 나는 그것을 구현하고 싶지 않다.


1
또한 : 어떤 경우에는 컴파일러가 프로그래머가 전혀 예상하지 못한 선택을하여 버그를 찾기가 어렵다는 것을 이해하기가 어려울 것입니다.
gnasher729

1
@ gnasher729 : 동의하지 않습니다. 나는이 기능을 가진 Haskell을 정기적으로 사용하며, 과부하 (즉, typeclass 인스턴스)의 선택에 물린 적이 없다. 뭔가 모호한 경우 유형 주석을 추가하도록 강요합니다. 그리고 나는 여전히 매우 유용하기 때문에 내 언어로 풀 타입 추론을 구현하기로 결정했습니다. 이 답변은 악마의 옹호자를 연주했습니다.
Jon Purdy

4

모호하기 때문입니다. C #을 예로 사용 :

var foo = method(42);

어떤 과부하를 사용해야합니까?

아마, 그것은 약간 불공평했을 것입니다. 가상 언어로 말하지 않으면 컴파일러가 사용할 유형을 파악할 수 없습니다. 따라서 귀하의 언어에서는 암시 적 타이핑이 불가능하며 익명의 메소드와 Linq도 함께 사용됩니다 ...

이건 어때? 요점을 설명하기 위해 서명을 약간 재정의했습니다.

short method(int num) { ... }
int method(int num) { ... }

....

long foo = method(42);

int과부하 또는 과부하를 사용해야합니까 short? 우리는 단순히 알지 못합니다 [int] method1(num). 구문 으로 지정해야 합니다. 정직 하게 파싱하고 쓰는 것은 약간의 고통 입니다.

long foo = [int] method(42);

문제는 놀랍게도 C #의 일반 메소드와 비슷한 구문입니다.

long foo = method<int>(42);

(C ++과 Java는 비슷한 기능을 가지고 있습니다.)

즉, 언어 디자이너는 구문 분석을 단순화하고 훨씬 강력한 언어 기능을 사용하기 위해 다른 방식으로 문제를 해결하기로 결정했습니다.

컴파일러에 대해 잘 모른다고 말합니다. 문법과 파서에 대해 배우는 것이 좋습니다. 문맥 자유 문법이 무엇인지 이해하면 모호성이 왜 나쁜지에 대해 훨씬 더 잘 이해할 수 있습니다.


제네릭에 대한 좋은 지적,하지만 당신은 가미하여 한 것으로 나타났습니다 short하고 int.
Robert Harvey

네, @RobertHarvey가 옳습니다. 요점을 설명하기 위해 예를 들어 고군분투했습니다. methodshort 또는 int를 반환하고 유형이 long으로 정의 되면 더 잘 작동 합니다.
RubberDuck

조금 나아 보입니다.
RubberDuck

리턴 타입 오버로드가있는 언어에서 타입 유추, 익명 서브 루틴 또는 모나드 이해를 할 수 없다는 주장은 구매하지 않습니다. 하스켈은 그것을하고 하스켈은 세 가지를 모두 가지고 있습니다. 파라 메트릭 다형성도 있습니다. long/ int/ 에 대한 요점은 short반환 유형 오버로드보다 하위 유형 지정 및 암시 적 변환의 복잡성에 대한 것입니다. 결국 숫자 리터럴 C ++, Java, C♯ 및 기타 여러 유형의 반환 유형에 과부하가 걸리므로 문제가되지 않습니다. 가장 구체적인 / 일반 유형을 선택하는 등의 규칙을 간단하게 만들 수 있습니다.
Jörg W Mittag

@ JörgWMittag 내 요점은 불가능하게 만드는 것이 아니라 불필요하게 복잡하게 만드는 것입니다.
RubberDuck

0

모든 언어 기능은 복잡성을 추가하므로 모든 기능에서 발생하는 불가피한 문제, 코너 케이스 및 사용자 혼란을 정당화 할 수있는 충분한 이점을 제공해야합니다. 대부분의 언어 에서이 언어는 정당화하기에 충분한 이점을 제공하지 않습니다.

대부분의 언어에서 표현식 method1(2)은 명확한 유형과 다소 예측 가능한 반환 값을 가질 것으로 예상합니다 . 그러나 반환 값에 오버로드를 허용하면 주변 컨텍스트를 고려하지 않고 해당 표현식이 일반적으로 무엇을 의미하는지 알 수 없습니다. unsigned long long foo()구현이 return method1(2)?로 끝나는 메소드 가있을 때 어떤 일이 발생하는지 고려하십시오 . 그것이 long-returning 과부하 또는 -returning 과부하 를 호출 int하거나 단순히 컴파일러 오류를 제공해야합니까?

또한 반환 유형에 주석을 달아 컴파일러를 도와야하는 경우 더 많은 구문을 발명 할뿐만 아니라 (위에 언급 된 기능이 존재하도록 허용하는 모든 비용을 발생시킵니다) 효과적으로 만드는 것과 동일한 일을하고 있습니다 "일반"언어로 된 다른 이름의 두 가지 방법 가 [long] method1(2)보다 직관적 long_method1(2)?


반면에 매우 강력한 정적 유형 시스템을 가진 Haskell과 같은 일부 기능 언어는 이러한 유형의 동작을 허용합니다. 유형 유추는 강력하기 때문에 반환 언어에 주석을 달 필요가 거의 없기 때문입니다. 그러나 이러한 언어는 모든 언어가 순수하고 참조가 투명해야 할뿐만 아니라 기존 언어보다 유형 안전을 실제로 시행하기 때문에 가능합니다. 대부분의 OOP 언어에서 실현 가능한 것은 아닙니다.


2
"모든 기능을 순수하고 참조가 투명해야하는 것과 함께": 리턴 타입 오버로드를 더 쉽게 만드는 방법은 무엇입니까?
Giorgio

@Giorgio 그것은하지 않습니다-Rust는 기능 순결을 강요하지 않으며 여전히 리턴 타입 오버로드를 수행 할 수 있습니다 (Russ에서의 오버로드는 다른 언어와 매우 다르지만 (템플릿을 사용하여 오버로드 만 가능))
Idan Arye

[long]과 [int] 부분은 명시 적으로 메소드를 호출하는 방법을 가지고 있었으며, 대부분의 경우 메소드를 호출하는 방법은 메소드 실행에 지정된 변수 유형에서 직접 추론 할 수 있습니다.
user2638180

0

그것은 이다 스위프트에서 사용할 미세이 작동합니다. 분명히 양쪽에 모호한 유형을 가질 수 없으므로 왼쪽에 알려야합니다.

간단한 인 코드 / 디코드 API 에서 이것을 사용했습니다 .

public protocol HierDecoder {
  func dech() throws -> String
  func dech() throws -> Int
  func dech() throws -> Bool

init, 객체 와 같이 매개 변수 유형이 알려진 호출은 매우 간단하게 작동합니다.

    private static let typeCode = "ds"
    static func registerFactory() {
        HierCodableFactories.Register(key:typeCode) {
            (from) -> HierCodable in
            return try tgDrawStyle(strokeColor:from.dech(), fillColor:from.dechOpt(), lineWidth:from.dech(), glowWidth: from.dech())
        }
    }
    func typeKey() -> String { return tgDrawStyle.typeCode }
    func encode(to:HierEncoder) {
        to.ench(strokeColor)
        to.enchOpt(fillColor)
        to.ench(lineWidth)
        to.ench(glowWidth)
    }

주의를 기울이면 dechOpt위 전화가 나타납니다. 호출 컨텍스트가 선택 사항이라는 기대를 일으킬 수 있기 때문에 미분기가 옵션을 반환하는 동일한 함수 이름을 오버로드하는 것이 너무 오류가 발생하기 어려운 어려운 방법을 발견했습니다.


-5
int main() {
    auto var1 = method1(1);
}

이 경우 컴파일러는 a) 모호하기 때문에 호출을 거부 할 수 있습니다 .b) 첫 번째 / 마지막 호출을 선택하십시오 .c) 유형을 떠나 var1유형 유추를 진행하고 다른 표현식 var1이 선택하는 사용 유형을 결정하자마자 올바른 구현. 결론적으로, 유형 유추가 사소하지 않은 경우를 보여주는 것은 그 유형 유추 이외의 점이 일반적으로 사소하지 않다는 것을 거의 증명하지 못합니다.
back2dos

1
강력한 주장이 아닙니다. 예를 들어 Rust는 형식 유추를 많이 사용하며, 일부 경우, 특히 제네릭의 경우 어떤 형식이 필요한지 알 수 없습니다. 이러한 경우 유형 유추에 의존하는 대신 유형을 명시 적으로 지정하면됩니다.
8bittree

1
어 ... 오버로드 된 반환 유형을 보여주지 않습니다. method1특정 유형을 반환하려면 선언해야합니다.
로봇 고트
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.