방법은 구문 설탕보다 더 많은 것이 있습니까? [닫은]


19

메소드가 다형성의 유형을 오버로드합니까? 나에게 그것은 같은 이름과 다른 매개 변수를 가진 메소드의 차별화와 같습니다. 지금 stuff(Thing t)stuff(Thing t, int n)멀리 컴파일러로 완전히 다른 방법은 런타임은 우려하고있다.

그것은 호출자 측에서 다른 종류의 물체-다형성에 대해 다르게 작용하는 동일한 방법이라는 착시를 만듭니다. 실제로 때문에 그러나 그것은 단지 환상의 stuff(Thing t)stuff(Thing t, int n)완전히 다른 방법이 있습니다.

방법은 구문 설탕보다 더 많은 것이 있습니까? 뭔가 빠졌습니까?


구문 설탕에 대한 일반적인 정의는 그것이 순전히 국소 적이라는 것입니다 . 코드 조각을 '달콤한'것으로 변경하거나 그 반대로 변경한다는 것은 프로그램의 전체 구조에 영향을 미치지 않는 로컬 변경과 관련이 있습니다. 그리고 메소드 오버로딩이이 기준에 정확하게 맞는다고 생각합니다. 다음을 보여주는 예를 살펴 보겠습니다.

수업을 고려하십시오 :

class Reader {
    public String read(Book b){
        // .. translate the book to text
    }
    public String read(File b){
        // .. translate the file to text
    }
}

이제이 클래스를 사용하는 다른 클래스를 고려하십시오.

/* might not be the best example */
class FileProcessor {
    Reader reader = new Reader();
    public void process(File file){
        String text = reader.read(file);
        // .. do stuff with the text
    }
}

괜찮아. 이제 메소드 오버로딩을 일반 메소드로 바꾸면 무엇이 변경되어야하는지 보자.

read의 방법 Reader으로 변경 readBook(Book)하고 readFile(file). 그들의 이름을 바꾸는 것의 문제.

의 호출 코드가 FileProcessor약간 reader.read(file)변경됩니다 :로 변경되었습니다 reader.readFile(file).

그리고 그게 다야.

보시다시피, 메소드 오버로드 사용과 사용하지 않는 것의 차이점은 순전히 로컬 입니다. 그래서 이것이 순수한 구문 설탕으로 자격이 있다고 생각하는 이유입니다.

당신의 의견이 있다면, 뭔가 빠졌을 수도 있습니다.


48
결국, 모든 프로그래밍 언어 기능은 원시 어셈블러의 구문 설탕입니다.
Philipp

31
@Philipp : 죄송 합니다만, 정말 바보 같은 말입니다. 프로그래밍 언어는 구문이 아니라 시맨틱에서 유용성을 이끌어냅니다. 타입 시스템과 같은 기능은 실제로 더 많은 것을 써야 할지라도 실제 보장을 제공합니다 .
back2dos

3
스스로에게 물어보십시오 : 연산자 과부하가 단지 구문 설탕입니까? 그 질문에 대한 답은 무엇이든 당신이 묻는 질문에 대한 답이기도합니다.)
back2dos

5
@ back2dos : 전적으로 동의합니다. 나는 "모든 것이 어셈블러를위한 문법 설탕"이라는 문구를 너무 자주 읽었고, 분명히 틀렸다. 구문 설탕은 새로운 의미를 추가하지 않는 기존 구문의 대안 (아마도 더 좋은) 구문입니다.
Giorgio

6
@Giorgio : 맞아! Matthias Felleisen의 표식에 대한 표현에는 정확한 정의가 있습니다. 기본적으로 구문 설탕은 순전히 로컬입니다. 언어 기능의 사용을 제거하기 위해 프로그램의 전역 구조를 변경해야하는 경우 구문 설탕이 아닙니다. 어셈블러에서 다형성 OO 코드를 재 작성 IE는 일반적으로 글로벌 파견 로직, 추가 포함 되지 때문에 OO, 순수하게 지역입니다 하지 "어셈블러 단지 문법 설탕".
Jörg W Mittag

답변:


29

이에 대한 답을 위해서는 먼저 "구문 설탕"에 대한 정의가 필요합니다. 나는 Wikipedia 's 와 함께 갈 것이다 .

컴퓨터 과학에서, 구문 설탕은 물건을보다 쉽게 ​​읽거나 표현할 수 있도록 설계된 프로그래밍 언어 내의 구문입니다. 그것은 인간이 사용하기에 언어를 "달콤하게"만든다 : 사물은 더 명확하고 간결하게 표현되거나 다른 스타일로 선호 될 수있다.

[...]

구체적으로, 언어의 구조는 언어가 할 수있는 일에 영향을 미치지 않고 언어에서 제거 될 수있는 경우 구문 설탕이라고합니다.

따라서이 정의에 따라 Java의 varargs 또는 Scala의 이해력과 같은 기능은 구문 설탕입니다. 언어로 할 수있는 일을 바꾸지 마십시오.

그러나 메소드 오버로드는이 정의에서 구문 설탕이 아닙니다. 기본 언어를 제거하면 언어가 근본적으로 변경되므로 더 이상 인수에 따라 고유 한 동작으로 디스패치 할 수 없습니다.

사실, 메소드의 인수에 액세스 할 수있는 방법이있는 한 메소드 오버로딩을 시뮬레이션 할 수 있으며 주어진 인수에 따라 "if"구문을 사용할 수 있습니다. 그러나 만약 당신이 그 구문 설탕을 고려한다면, 당신은 마찬가지로 구문 설탕이기 위해 튜링 머신 위에있는 것을 고려해야합니다.


22
오버로드를 제거해도 언어가 수행 할 수있는 작업은 변경되지 않습니다. 여전히 이전과 똑같은 일을 할 수 있습니다. 몇 가지 방법의 이름 만 바꾸면됩니다. 그것은 설탕 제거 루프보다 더 사소한 변화입니다.
Doval

9
내가 말했듯이, 기계 언어를 포함한 모든 언어는 단순히 튜링 머신 위에 구문 설탕이라는 접근 방식을 취할 수 있습니다.
kdgregory

9
나는이 방법은 오버로드시피 단순히 당신이 할 수 있도록 sum(numbersArray)하고 sum(numbersList)대신 sumArray(numbersArray)하고 sumList(numbersList). 나는 Doval에 동의합니다. 그것은 단순한 syntatic sugar처럼 보입니다.
Aviv Cohn

3
대부분의 언어. 구현하는 시도 instanceof, 클래스, 상속, 인터페이스, 제네릭, 반사 또는 사용 액세스 지정자 if, while와,와 부울 연산자, 동일한 의미를 . 코너 케이스가 없습니다. 이러한 구문의 특정 용도와 동일한 것을 계산하는 것은 어렵지 않습니다. 부울 논리 및 분기 / 루핑을 사용하여 무엇이든 계산할 수 있다는 것을 이미 알고 있습니다. 나는 그들이 제공하는 정적 보장을 포함하는 언어 기능의 의미의 완벽한 복사본을 구현하는 방법을 부탁 해요 (컴파일 시간 체크가 여전히 컴파일시에 수행해야합니다.)
Doval

6
@Doval, kdgregory : 구문 설탕을 정의하려면 일부 의미와 관련하여 정의해야합니다. 당신이 가진 유일한 의미론이 "이 프로그램은 무엇을 계산합니까?"라면, 모든 것이 튜링 머신의 구문 설탕이라는 것이 분명합니다. 반면에 객체와 객체에 대한 특정 작업에 대해 말할 수있는 의미가있는 경우 언어가 여전히 Turing-complete 일 수 있지만 특정 구문을 제거하면 더 이상 해당 작업을 표현할 수 없습니다.
Giorgio

13

구문 설탕이라는 용어는 전형적으로 특징이 치환에 의해 정의되는 경우를 지칭한다. 언어는 기능이 수행하는 기능을 정의하지 않고 다른 기능과 정확히 동등한 기능을 정의합니다. 예를 들어 for-each 루프

for(Object alpha: alphas) {
}

된다 :

for(Iterator<Object> iter = alpha.iterator(); iter.hasNext()) {
   alpha = iter.next();
}

또는 가변 인수로 함수를 가져옵니다.

void foo(int... args);

foo(3, 4, 5);

다음이된다 :

void Foo(int[] args);

foo(new int[]{3, 4, 5});

따라서 다른 기능 측면에서 기능을 구현하기 위해 구문을 간단하게 대체합니다.

메소드 오버로딩을 살펴 보자.

void foo(int a);
void foo(double b);

foo(4.5);

이것은 다음과 같이 다시 작성할 수 있습니다.

void foo_int(int a);
void foo_double(double b);

foo_double(4.5);

그러나 이것과 동일하지 않습니다. Java 모델에서 이것은 다른 것입니다. 만들 함수를 foo(int a)구현하지 않습니다 foo_int. Java는 모호한 함수에 재미있는 이름을 지정하여 메소드 오버로드를 구현하지 않습니다. 구문 설탕으로 계산하려면 Java는 실제로 작성 foo_int하고 foo_double기능 하는 것처럼 가장해야 하지만 그렇지는 않습니다.


2
구문 설탕의 변환이 사소한 것이라고 말한 사람은 아무도 없다고 생각합니다. 그렇더라도 But, the transformation isn't trivial. At the least, you have to determine the types of the parameters.유형을 결정할 필요가 없기 때문에 매우 스케치 적이라는 주장을 발견했습니다 . 컴파일 타임에 알려져 있습니다.
Doval

3
"구문 설탕으로 계산하기 위해, 자바는 실제로 foo_int와 foo_double 함수를 작성했다고 가정하고 있지만 그렇지 않다." -우리가 다형성이 아닌 오버로드 방법에 대해 이야기하는 한, foo(int)/ foo(double)foo_int/ 의 차이점은 foo_double무엇입니까? 정말 잘 자바 모르지만 그 이름 변경이 정말 잘 JVM (에서 일어나는 상상 - 아마 사용 foo(args)하기보다는 foo_args- 그것은 기호 맹 글링와 C ++에서 적어도 (OK 않습니다 - 기호 맹 글링 기술적 구현 세부 사항이 아닌 부분이다 ) 언어의.
마치에이 Piechotka

2
@Doval : "저는 문법 설탕의 변환이 사소한 것이라고 말한 사람이 없다고 생각합니다." – 사실이지만 로컬 이어야 합니다. 내가 아는 구문 설탕의 유일한 유용한 정의는 언어 표현력에 관한 Matthias Felleisen의 유명한 논문에서 나온 것이며, 기본적으로 언어 L + y 로 작성된 프로그램을 다시 작성할 수 있다면 (즉 , 일부 기능 y를 가진 언어 L ) 언어 L (즉, 일부 기능이없는 언어 Y ) (즉, 로컬 변경) 프로그램의 글로벌 구조를 변경하지 않고, 다음 예는 에 문법적이고 L은 + Y 및 수행
Jörg W Mittag

2
L 의 표현력을 높이 지 않습니다 . 당신은 그러나 를 사용하여 프로그램의 글로벌 구조를 변경해야하는 경우, 즉 그 수행, 다음은 하지 구문 설탕과 않는 사실 메이크에서 L + Y 보다 더 많은 표현 L . 예를 들어, for루프가 향상된 Java는 Java가없는 Java보다 표현력이 우수하지 않습니다. (더 좋고, 간결하고, 더 읽기 쉽고, 모든 것이 더 좋으며, 나는 주장하지만 표현력이 좋지는 않습니다 .) 그러나 과부하 사례에 대해서는 확신하지 못합니다. 아마 논문을 다시 읽어야 할 것입니다. 내 직감은 말한다 이다 문법 설탕,하지만 난 모르겠어요.
Jörg W Mittag

2
@MaciejPiechotka, 언어 정의의 일부로 함수의 이름이 바뀌어 해당 이름으로 함수에 액세스 할 수 있다면 구문 설탕이라고 생각합니다. 그러나 구현 세부 사항으로 숨겨져 있기 때문에 구문 설탕이 아닌 것으로 생각합니다.
Winston Ewert

8

그 이름을 깎아내는 작업을 고려할 때, 그것은 구문 설탕 이상일 필요는 없습니까?

호출자가 그렇지 않은 경우 동일한 함수를 호출한다고 상상할 수 있습니다. 그러나 그는 모든 기능 의 실제 이름을 알 수있었습니다 . 유형이 지정되지 않은 변수를 유형이 지정된 함수에 전달하여 지연된 다형성을 달성 할 수 있고 이름에 따라 호출이 올바른 버전으로 갈 수 있도록 유형을 설정 한 경우에만 이것이 진정한 언어 기능이됩니다.

불행히도, 나는 이것을하는 언어를 본 적이 없다. 애매 모호한 경우, 이러한 컴파일러는이를 해결하지 못하며, 작가는이를 해결해야한다고 주장합니다.


찾고있는 기능을 "다중 디스패치"라고합니다. Haskell, Scala 및 (4.0 이후) C #을 포함하여 많은 언어가이를 지원합니다.
Iain Galloway

클래스의 매개 변수를 스트레이트 메서드 오버로드와 분리하고 싶습니다. 스트레이트 메소드 오버로딩 사례에서 프로그래머는 모든 버전을 작성하고 컴파일러는 하나를 선택하는 방법 만 알고 있습니다. 그것은 단지 구문상의 설탕이며, 다중 디스패치에도 간단한 이름 관리로 해결됩니다. --- 클래스에 매개 변수가있는 경우 컴파일러는 필요에 따라 코드를 생성하고이를 완전히 변경합니다.
Jon Jay Obermark

2
당신이 오해하는 것 같아요 방법의 파라미터 중 하나가 예를 들어, C #으로, dynamic다음 과부하 해상도하지 컴파일시에, 실행시에 발생 . 이것이 다중 디스패치이며, 이름을 바꾸어 함수로 복제 할 수 없습니다.
Iain Galloway

꽤 멋지다. 그러나 여전히 변수 유형을 테스트 할 수 있으므로 구문 설탕에 오버레이 된 내장 함수 일뿐입니다. 언어 기능이지만 간신히 있습니다.
Jon Jay Obermark

7

언어에 따라 구문 설탕인지 아닌지.

예를 들어 C ++에서는 복잡하지 않고 오버로드와 템플릿을 사용하여 작업을 수행 할 수 있습니다 (템플릿의 모든 인스턴스를 수동으로 작성하거나 많은 템플릿 매개 변수를 추가).

동적 디스패치는 과부하의 한 형태이며 일부 매개 변수에서 동적으로 해결됩니다 (일부 언어의 경우 특수 언어만 제한적이지만 모든 언어가 그렇게 제한적이지는 않습니다).


본질적으로 부정확 할 때 다른 답변이 어떻게 더 잘 수행되는지 잘 모르겠습니다.
Telastyn

5

현대 언어의 경우, 그것은 단지 구문 설탕입니다. 완전히 언어에 구애받지 않는 방식으로, 그 이상입니다.

이전에이 답변은 단순히 그것이 설탕이라는 것이 아니라고 언급했지만, 주석에서 볼 수 있듯이, Falco는 현대 언어가 모두 누락 된 것으로 보이는 퍼즐 조각이 있다고 지적했습니다. 메소드 오버로드와 동일한 단계에서 호출 할 함수의 동적 결정을 혼합하지 않습니다. 이것은 나중에 설명 될 것입니다.

이 여기 왜 해야 더합니다.

메서드 오버로딩과 형식화되지 않은 변수를 모두 지원하는 언어를 고려하십시오. 다음과 같은 메소드 프로토 타입을 가질 수 있습니다.

bool someFunction(int arg);

bool someFunction(string arg);

일부 언어에서는 컴파일 타임에 주어진 코드 줄에 의해 호출되는 것을 아는 것으로 아마 사임 될 것입니다. 그러나 일부 언어에서는 모든 변수가 입력되는 것은 아니며 (또는 모두 암시 적으로 유형이 지정 Object되거나 다른 것임) 키가 다른 유형의 값에 매핑되는 사전을 작성하는 것을 상상해보십시오.

dict roomNumber; // some hotels use numbers, some use letters, and some use
                 // alphanumerical strings.  In some languages, built-in dictionary
                 // types automatically use untyped values for their keys to map to,
                 // so it makes more sense then to allow for both ints and strings in
                 // your code.

그렇다면 someFunction그 방 번호 중 하나에 적용 하려면 어떻게해야 합니까? 당신은 이것을 호출합니다 :

someFunction(roomNumber[someSortOfKey]);

인가 someFunction(int)라고, 또는 someFunction(string)라고? 여기에 이들이 완전히 직교적인 방법이 아닌, 특히 고급 언어의 한 예가 있습니다. 언어는 런타임 중에 어떤 것을 호출해야하는지 파악해야하므로 여전히 적어도 동일한 방법으로 간주해야합니다.

단순히 템플릿을 사용하지 않는 이유는 무엇입니까? 왜 형식화되지 않은 인수를 사용하지 않습니까?

유연성과 세밀한 제어. 때로는 템플릿 / 형식화되지 않은 인수를 사용하는 것이 더 나은 방법이지만 때로는 그렇지 않습니다.

예를 들어, 각각 inta와 a string를 인수로 취하는 두 개의 메소드 서명이 있지만 각 서명에서 순서가 다른 경우를 고려해야합니다 . 각 서명의 구현이 거의 동일한 작업을 수행하지만 약간 다른 트위스트를 수행 할 수 있기 때문에이 작업을 수행 할 충분한 이유가있을 수 있습니다. 예를 들어 로깅이 다를 수 있습니다. 또는 그들이 똑같은 일을하더라도 인수가 지정된 순서대로 특정 정보를 자동으로 얻을 수 있습니다. 기술적으로 pseudo-switch 문을 사용하여 전달 된 각 인수의 유형을 결정할 수는 있지만 지저분합니다.

이 다음 예제는 나쁜 프로그래밍 연습입니까?

bool stringIsTrue(int arg)
{
    if (arg.toString() == "0")
    {
        return false;
    }
    else
    {
        return true;
    }
}

bool stringIsTrue(Object arg)
{
    if (arg.toString() == "0")
    {
        return false;
    }
    else
    {
        return true;
    }
}

bool stringIsTrue(string arg)
{
    if (arg == "0")
    {
        return false;
    }
    else
    {
        return true;
    }
}

그렇습니다. 이 특정 예제에서 누군가가 이것을 특정 기본 유형에 적용하려고 시도하지 않고 예기치 않은 동작 (좋은 일이 될 수 있음)을 되찾지 못하게 할 수 있습니다. 그러나 위의 코드를 축약하고 실제로 모든 기본 유형과 Objects에 대한 과부하가 있다고 가정 해 봅시다 . 그런 다음 다음 코드 비트가 더 적합합니다.

bool stringIsTrue(untyped arg)
{
    if (arg.toString() == "0")
    {
        return false;
    }
    else
    {
        return true;
    }
}

그러나 이것을 ints와 strings에 대해서만 사용해야 한다면, 더 단순하거나 복잡한 조건에 따라 true를 반환하려면 어떻게해야합니까? 그런 다음 오버로드를 사용해야 할 합당한 이유가 있습니다.

bool appearsToBeFirstFloor(int arg)
{
    if (arg.digitAt(0) == 1)
    {
        return true;
    }
    else
    {
        return false;
    }
}

bool appearsToBeFirstFloor(string arg)
{
    string firstCharacter = arg.characterAt(0);
    if (firstCharacter.isDigit())
    {
        return appearsToBeFirstFloor(int(firstCharacter));
    }
    else if (firstCharacter.toUpper() == "A")
    {
        return true;
    }
    else
    {
        return false;
    }
}

그런데 왜 그 함수에 두 개의 다른 이름을 부여하지 않습니까? 당신은 여전히 ​​같은 양의 세밀한 컨트롤을 가지고 있습니까?

앞에서 언급했듯이 일부 호텔은 숫자를 사용하고 일부는 문자를 사용하며 일부는 숫자와 문자를 혼합하여 사용하기 때문입니다.

appearsToBeFirstFloor(roomNumber[someSortOfKey]);

// will treat ints and strings differently, without you having to write extra code
// every single spot where the function is being called

이것은 여전히 ​​실제 생활에서 사용하는 것과 똑같은 정확한 코드는 아니지만, 내가 잘 만드는 요점을 설명해야합니다.

그러나 ... 이것이 현대 언어의 구문 설탕 이상이 아닌 이유입니다.

Falco는 현재 언어가 기본적으로 동일한 단계 내에서 메소드 오버로드와 동적 함수 선택을 혼합하지 않는다는 의견에서 요점을 제기했습니다. 이전에 특정 언어가 작동하는 것을 이해 한 방법은 appearsToBeFirstFloor위의 예제에서 오버로드 할 수 있다는 것 입니다. 언어는 런타임에 유형이 지정되지 않은 변수의 런타임 값에 따라 호출 할 함수 버전을 결정합니다. 이러한 혼동은 부분적으로 ActionScript 3.0과 같은 ECMA 정렬 언어를 사용하여 런타임에 특정 코드 행에서 어떤 함수가 호출 될지를 쉽게 무작위화할 수있게함으로써 발생합니다.

아시다시피 ActionScript 3는 메서드 오버로드를 지원하지 않습니다. VB.NET의 경우 명시 적으로 형식을 할당하지 않고 변수를 선언하고 설정할 수 있지만 이러한 변수를 오버로드 된 메서드의 인수로 전달하려고 할 때 여전히 런타임 값을 읽고 호출 할 메서드를 결정하지는 않습니다. 대신 유형의 인수가 Object있거나 유형이 없거나 이와 비슷한 다른 메소드를 찾고 싶습니다. 따라서 위 의 intvs. string예제는 해당 언어로 작동하지 않습니다. C ++에는 void 포인터 또는 이와 같은 다른 메커니즘을 사용할 때 비슷한 문제가 있지만 컴파일 타임에 유형을 수동으로 명확하게해야합니다.

첫 번째 헤더가 말한 것처럼 ...

현대 언어의 경우, 그것은 단지 구문 설탕입니다. 완전히 언어에 구애받지 않는 방식으로, 그 이상입니다. 위의 예에서와 같이 메소드 오버로드를보다 유용하고 관련성있게 만드는 것은 실제로 기존 언어에 추가하기에 좋은 기능 일 수도 있고 (AS3에 대해 광범위하게 요청 된 것처럼), 또는 새로운 절차 적 / 객체 지향 언어의 생성.


3
컴파일 타임이 아닌 런타임에 함수 디스패치를 ​​실제로 처리하는 언어를 지정할 수 있습니까? 내가 아는 모든 언어는 어떤 함수가 호출되는지 확실히 컴파일 타임이 필요합니다.
Falco

@Falco ActionScript 3.0은 런타임에이를 처리합니다. : 당신은, 예를 들어, 반환 무작위로 세 개의 문자열 중 하나, 그리고 무작위로 세 가지 기능 중 하나를 호출의 반환 값을 사용하는 함수를 사용할 수있는 this[chooseFunctionNameAtRandom](); 경우 chooseFunctionNameAtRandom()반환하거나 "punch", "kick"또는 "dodge", 당신은 thusly 히 무작위로 매우 간단하게 구현할 수 있습니다 예를 들어 플래시 게임에서 적의 AI에
Panzercrisis

1
예-그러나 동적 함수 디스패치를 ​​얻기위한 실제 의미 론적 방법입니다. Java도 마찬가지입니다. 그러나 그것들은 오버로딩과는 다르며, 오버로딩은 정적이고 단순한 구문 설탕이며 동적 디스패치 및 상속은 새로운 기능을 제공하는 실제 언어 기능입니다!
팔코

1
... 또한 기본 클래스 포인터뿐만 아니라 C ++에서 void 포인터를 시도했지만 컴파일러는 함수에 전달하기 전에 명확하게 설명하고 싶었습니다. 이제이 답변을 삭제할지 궁금합니다. 언어는 거의 항상 같은 명령이나 문장에서 함수 오버로드와 동적 함수 선택을 결합하는 데 거의 항상 도움이되는 것처럼 보이기 시작하지만 마지막 순간에는 물러납니다. 그래도 좋은 언어 기능이 될 것입니다; 누군가 이것을 가지고있는 언어를 만들어야 할 수도 있습니다.
Panzercrisis

1
답을 그대로 두십시오. 아마도 답변에 대한 의견에서 조사한 내용을 포함시키는 것에 대해 생각해보십시오.
팔코

2

그것은 "구문 설탕"의 정의에 달려 있습니다. 나는 내 마음에 오는 몇 가지 정의를 다루려고 노력할 것이다.

  1. 기능은 기능을 사용하는 프로그램이 기능을 사용하지 않는 다른 프로그램으로 항상 번역 될 수있는 경우 구문 설탕입니다.

    여기서 변환 할 수없는 기본 피처 세트가 있다고 가정합니다. 즉, "피처 Y를 사용하여 피처 X를 대체 할 수 있습니다"및 "피처 Y를 피처 X로 대체 할 수있는"종류의 루프는 없습니다. 둘 중 하나가 사실 인 경우, 다른 특징은 첫 번째 특징이 아닌 특징으로 표현 될 수 있거나 기본 특징입니다.

  2. 정의 1 과 동일 하지만 번역 된 프로그램이 첫 번째 유형만큼 안전하다는 추가 요구 사항이 있습니다. 즉, 설탕을 제거하면 모든 종류의 정보를 잃지 않습니다.

  3. OP의 정의 : 기능은 번역이 프로그램 구조를 변경하지 않고 "로컬 변경"만 필요한 경우 구문 설탕입니다.

과부하의 예를 Haskell을 예로 들어 봅시다. Haskell은 타입 클래스를 통해 사용자 정의 오버로딩을 제공합니다. 예를 들어 +*연산은 Num유형 클래스에 정의되며 해당 클래스의 (완전한) 인스턴스가있는 모든 유형을와 함께 사용할 수 있습니다 +. 예를 들면 다음과 같습니다.

instance Num a => Num (b, a) where
    (x, y) + (_, y') = (x, y + y')
    -- other definitions

("Hello", 1) + ("World", 3) -- -> ("Hello", 4)

Haskell의 유형 클래스에 대해 잘 알려진 것은 클래스를 제거 할 수 있다는 입니다. 즉, 타입 클래스를 사용하는 프로그램을 사용하지 않는 동등한 프로그램에서 번역 할 수 있습니다.

번역은 매우 간단합니다.

  • 클래스 정의가 주어지면 :

    class (P_1 a, ..., P_n a) => X a where
        op_1 :: t_1   ... op_m :: t_m
    

    대수 데이터 형식으로 변환 할 수 있습니다.

    data X a = X {
        X_P_1 :: P_1 a, ... X_P_n :: P_n a,
        X_op_1 :: t_1, ..., X_op_m :: t_m
    }
    

    이곳까지 X_P_iX_op_i있습니다 선택기 . 즉, 값에 X a적용 X_P_1되는 유형의 값이 주어지면 해당 필드에 저장된 값을 반환하므로 유형 X a -> P_i a(또는 X a -> t_i)의 함수입니다 .

    A의 매우 거친 anology 당신은 유형에 대한 값을 생각할 수있는 X a등의 struct경우에는 다음의 및 x유형 인 X a표현 :

    X_P_1 x
    X_op_1 x
    

    다음과 같이 보일 수 있습니다 :

    x.X_P_1
    x.X_op_1
    

    (명명 된 필드 대신 위치 필드 만 사용하는 것이 쉽지만 예제에서 명명 된 필드를 다루기 쉽고 보일러 플레이트 코드를 피하십시오).

  • 인스턴스 선언이 주어지면 :

    instance (C_1 a_1, ..., C_n a_n) => X (T a_1 ... a_n) where
        op_1 = ...; ...;  op_m = ...
    

    C_1 a_1, ..., C_n a_n클래스에 대한 사전이 주어진 유형에 대한 사전 값 (예 : 유형 값 X a)을 리턴 하는 함수로 변환 할 수 있습니다 T a_1 ... a_n.

    즉, 위의 예는 다음과 같은 함수로 변환 될 수 있습니다.

    f :: C_1 a_1 -> ... -> C_n a_n -> X (T a_1 ... a_n)
    

    ( n일 수도 있음 0).

    그리고 실제로 다음과 같이 정의 할 수 있습니다.

    f c1 ... cN = X {X_P_1=get_P_1_T, X_P_n=get_P_n_T,
                     X_op_1=op_1, ..., X_op_m=op_m}
        where
            op_1 = ...
            ...
            op_m = ...
    

    여기서 op_1 = ...하기 op_m = ...검색된 정의되어 instance선언하고는 get_P_i_T에 의해 정의되는 함수이다 P_i의 인스턴스 T(이 때문에 존재해야 타입 P_i들 중 수퍼있다 X).

  • 오버로드 된 함수를 호출했을 때 :

    add :: Num a => a -> a -> a
    add x y = x + y
    

    클래스 제약 조건과 관련하여 사전을 명시 적으로 전달하고 동등한 호출을 얻을 수 있습니다.

    add :: Num a -> a -> a -> a
    add dictNum x y = ((+) dictNum) x y
    

    클래스 제약 조건이 단순히 새로운 논쟁이 된 방법에 주목하십시오. +전에 설명 된대로 번역 프로그램은 선택입니다. 즉, add인수 유형에 대한 사전이 제공된 경우 변환 된 함수는 먼저 실제 함수를 "풀고"사용하여 결과를 계산 (+) dictNum한 다음이 함수를 인수에 적용합니다.

이것은 모든 것에 대한 매우 빠른 스케치입니다. 관심이 있으시면 Simon Peyton Jones et al.

다른 언어로도 과부하에 비슷한 접근법 사용할 있다고 생각합니다 .

그러나 이것은 구문 설탕의 정의가 (1)이면 과부하가 구문 설탕 이라는 것을 보여줍니다 . 당신이 그것을 제거 할 수 있기 때문에.

그러나 번역 된 프로그램은 원래 프로그램에 대한 일부 정보를 잃어 버립니다. 예를 들어 부모 클래스의 인스턴스가 존재하도록 강요하지 않습니다. (부모 사전을 추출하는 작업은 여전히 ​​해당 유형이어야하지만에 undefined대한 값을 작성 X y하지 않고 값을 만들 수 있도록 변환 또는 다른 다형성 값을 전달할 수 P_i y있으므로 번역이 모두 느슨해지지는 않습니다. 유형 안전). 따라서 (2)에 따르면 시냅스 설탕이 아닙니다.

(3)은. 대답이 '예'인지 '아니오'인지 모르겠습니다.

예를 들어 인스턴스 선언이 함수 정의가되기 때문에 아니요라고 대답합니다. 오버로드 된 함수는 새로운 매개 변수를 얻습니다 (정의와 모든 호출을 모두 변경 함을 의미합니다).

두 프로그램이 여전히 일대일로 매핑되므로 "구조"가 실제로 그렇게 많이 변경되지 않기 때문에 예라고 대답하고 싶습니다.


이것은 과부하에 의해 도입 된 실용적 장점이 너무 커서 "합성 당"과 같은 "유도체"용어를 사용하는 것이 정확하지 않다고 말하고 싶습니다 .

모든 Haskell 구문을 매우 간단한 핵심 언어 (실제로 컴파일 할 때 수행됨)로 번역 할 수 있으므로 대부분 의 Haskell 구문은 람다 미적분과 약간의 새로운 구문에 대한 "구문 설탕"으로 볼 수 있습니다. 그러나 Haskell 프로그램은 다루기가 훨씬 쉽고 간결하지만 번역 된 프로그램은 읽기 나 생각하기가 매우 어렵다는 데 동의 할 수 있습니다.


2

컴파일 타임에 인수 표현식의 정적 유형에 따라 디스패치가 해결되면 프로그래머가 정적 유형을 "인식"하는 경우 두 개의 다른 메소드를 다른 이름으로 대체하는 "구문 설탕"이라고 확실히 주장 할 수 있습니다. 오버로드 된 이름 대신 올바른 메소드 이름을 사용할 수 있습니다. 그것은이다 또한 정적 다형성의 한 형태하지만 제한된 형태로는 일반적으로 매우 강력한 아니다.

물론 변수의 유형을 변경할 때마다 호출하는 메서드의 이름을 변경해야하는 번거 로움이 있지만 C 언어에서는 관리하기 쉬운 번거 로움이 있으므로 C에는 함수 오버로드가 없습니다. 이제 일반 매크로가 있습니다).

C ++ 템플릿 및 사소한 정적 유형 공제를 수행하는 모든 언어에서 정적 유형 공제가 "구문 설탕"이라고 주장하지 않는 한 실제로 이것이 "합성 설탕"이라고 주장 할 수는 없습니다. 템플릿을 갖지 않는 것은 귀찮은 일이며, C ++의 맥락에서는 언어와 표준 라이브러리에 관용적이기 때문에 "관리 할 수없는 성가신"일 것입니다. 따라서 C ++에서 그것은 훌륭한 도우미가 아닌 언어의 스타일에 중요하기 때문에 "구문 설탕"이상으로 호출해야한다고 생각합니다.

Java에서는 예를 들어 PrintStream.print및의 과부하 수를 고려하여 편의성 이상을 고려할 수 있습니다 PrintStream.println. 그러나 DataInputStream.readXJava는 반환 유형에 과부하가 걸리지 않기 때문에 많은 방법이 있으므로 어떤 의미에서는 편의상입니다. 그것들은 모두 기본 유형입니다.

내가 수업을 경우 나는 자바에서 일어나는 일을 기억하지 않습니다 AB연장 O, 내가 방법을 과부하 foo(O), foo(A)foo(B)함께 일반에 다음과 <T extends O>내가 전화 foo(t)t의 인스턴스입니다 T. 경우 T입니다 A내가 과부하에 따라 파견을받을 수 있나요 아니면 내가 전화하면 같다 foo(O)?

전자의 경우 Java 메소드 오버로드는 C ++ 오버로드와 같은 방식으로 설탕보다 낫습니다. 귀하의 정의를 사용하여 Java에서 일련의 유형 검사를 로컬로 작성할 수 있다고 가정합니다 (새로운 과부하 foo에는 추가 검사가 필요 하기 때문에 깨지기 쉽습니다 ). 그 취약성을 받아들이는 것 외에도 콜 사이트에서 로컬 변경을 수행 할 수 없으므로 대신 일반 코드 작성을 포기해야합니다. 부풀어 오른 코드를 방지하는 것은 구문 설탕 일 수 있지만 깨지기 쉬운 코드를 방지하는 것이 그 이상이라고 주장합니다. 이런 이유로 정적 정적 다형성은 일반적으로 단순한 구문 설탕 이상의 것입니다. 정적 유형을 "알지 못하여"언어가 얼마나 멀리 도달 할 수 있는지에 따라 특정 언어의 상황이 다를 수 있습니다.


Java에서는 과부하가 컴파일 타임에 해결됩니다. 유형 삭제를 사용하면 다른 방법으로는 불가능합니다. 또한 경우에도 형 소거없이 T:Animal형이다 SiameseCat기존 오버로드되고 Cat Foo(Animal), SiameseCat Foo(Cat)Animal Foo(SiameseCat)경우, 이는 과부하 선택해야 T하다 SiameseCat?
supercat

@ supercat : 이해가됩니다. 그래서 나는 기억하지 않고 대답을 알아낼 수있었습니다 (물론 실행하십시오). 따라서 C ++ 오버로드가 일반 코드와 관련되는 것과 같은 방식으로 Java 오버로드는 설탕보다 낫지 않습니다 . 로컬 변환보다 더 나은 다른 방법이있을 가능성이 있습니다. 예제를 C ++로 변경하거나 실제 자바가 아닌 상상의 자바로 남겨 두어야하는지 궁금합니다.
Steve Jessop

메소드에 선택적 인수가있는 경우 오버로드가 도움이 될 수 있지만 위험 할 수도 있습니다. 줄 long foo=Math.round(bar*1.0001)*5이로 변경 되었다고 가정합니다 long foo=Math.round(bar)*5. bar예를 들어 123456789L과 같은 경우 시맨틱에 어떤 영향을 미칩니 까?
supercat

내가 진짜 위험을 주장 할 것 @supercat에서 암시 적 변환이 long에가 double.
Doval

@Doval :받는 사람 double?
supercat

1

"구문 설탕"은 쓸모 없거나 경박 한 것처럼 들리는 소리처럼 보입니다. 그렇기 때문에 질문이 많은 부정적인 답변을 유발하는 이유입니다.

그러나 당신이 옳습니다. 메서드 오버로드는 다른 메소드에 동일한 이름을 사용할 가능성을 제외하고 언어에 기능을 추가하지 않습니다. 매개 변수 유형을 명시 적으로 지정할 수 있으며 프로그램은 여전히 ​​동일하게 작동합니다.

패키지 이름에도 동일하게 적용됩니다. 문자열은 java.lang.String의 구문 설탕입니다.

사실, 같은 방법

void fun(int i, String c);

MyClass 클래스에서 "my_package_MyClass_fun_int_java_lang_String"과 같은 이름을 사용해야합니다. 이것은 방법을 고유하게 식별합니다. (JVM은 내부적으로 이와 같은 작업을 수행합니다). 그러나 당신은 그것을 쓰고 싶지 않습니다. 그래서 컴파일러가 fun (1, "one")을 작성하고 어떤 메소드인지 식별 ​​할 수 있습니다.

그러나 오버로드로 할 수있는 한 가지가 있습니다. 동일한 수의 인수로 메소드를 오버로드하면 컴파일러는 동일한 유형뿐만 아니라 인수가 일치하는 인수에 의해 주어진 인수에 가장 적합한 버전을 자동으로 파악합니다. 주어진 인수는 선언 된 인수의 서브 클래스입니다.

오버로드 된 절차가 두 개인 경우

addParameter(String name, Object value);
addParameter(String name, Date value);

날짜에 대한 특정 버전의 절차가 있다는 것을 알 필요는 없습니다. addParameter ( "hello", "world)는 첫 번째 버전을 호출하고 addParameter ("now ", new Date ())는 두 번째 버전을 호출합니다.

물론 완전히 다른 일을하는 다른 방법으로 메서드를 오버로드하지 않아야합니다.


1

흥미롭게도이 질문에 대한 답은 언어에 따라 다릅니다.

특히, 오버로드일반 프로그래밍 (*) 사이에는 상호 작용이 있으며 , 일반 프로그래밍이 구현되는 방식에 따라 구문 설탕 (Rust) 또는 절대적으로 필요한 (C ++) 것일 수 있습니다.

즉, 일반 프로그래밍이 명시 적 인터페이스 (Russ 또는 Haskell에서 유형 클래스 임)로 구현 된 경우 오버로드는 단지 구문상의 설탕입니다. 실제로 언어의 일부가 아닐 수도 있습니다.

반면, 일반적인 프로그래밍이 덕 타이핑 (동적 또는 정적)으로 구현 된 경우 메소드 이름은 필수 계약이므로 시스템이 작동하려면 과부하가 필수적입니다.

(*) 방법을 한 번 작성한다는 의미에서 다양한 유형으로 균일 한 방식으로 작동합니다.


0

어떤 언어에서는 단지 구문 설탕 일 것입니다. 그러나 그것이 설탕이라는 것은 당신의 관점에 달려 있습니다. 이 답변의 뒷부분에이 토론을 남겨 두겠습니다.

지금은 일부 언어에서는 확실히 구문 설탕이 아니라는 점에 주목하고 싶습니다. 적어도 동일한 것을 구현하기 위해 완전히 다른 논리 / 알고리즘을 사용할 필요가 없습니다. 재귀가 구문 설탕이라고 주장하는 것과 같습니다 (루프와 스택으로 모든 재귀 알고리즘을 작성할 수 있기 때문에).

대체하기 어려운 사용의 한 예는이 기능을 "함수 과부하"라고 부르지 않는 언어에서 비롯된 것입니다. 대신 "패턴 일치"라고합니다 (유형뿐만 아니라 값만 오버로드 할 수 있기 때문에 오버로드의 상위 집합으로 볼 수 있음).

Haskell의 피보나치 함수의 기본 순진한 구현은 다음과 같습니다.

fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

이 세 가지 기능은 다른 언어로 일반적으로 수행되는 것처럼 if / else로 대체 될 수 있습니다. 그러나 그것은 근본적으로 완전히 간단한 정의를 만듭니다.

fib n = fib (n-1) + fib (n-2)

피보나치 수열의 수학적 개념을 훨씬 더 복잡하고 직접적으로 표현하지는 않습니다.

따라서 때로는 다른 인수로 함수를 호출하는 것이 유일한 용도라면 구문 설탕 일 수 있습니다. 그러나 때로는 그보다 훨씬 더 근본적입니다.


이제 운영자 과부하가 무엇인지에 대한 논쟁은 설탕이 될 수 있습니다. 하나의 유스 케이스를 식별했습니다. 다른 인수를 취하는 유사한 함수를 구현하는 데 사용할 수 있습니다. 그래서:

function print (string x) { stdout.print(x) };
function print (number x) { stdout.print(x.toString) };

대안으로 다음과 같이 구현할 수 있습니다.

function printString (string x) {...}
function printNumber (number x) {...}

또는:

function print (auto x) {
    if (x instanceof String) {...}
    if (x instanceof Number) {...}
}

그러나 연산자 오버로드는 선택적 인수를 구현하는 데 도움이 될 수도 있습니다 (일부 언어에는 연산자 오버로드가 있지만 선택적 인수는 없습니다).

function print (string x) {...}
function print (string x, stream io) {...}

구현하는 데 사용될 수 있습니다 :

function print (string x, stream io=stdout) {...}

이러한 언어 (google "Ferite language")에서 연산자 오버로드를 제거하면 선택적 인수 인 하나의 기능이 대폭 제거됩니다. 두 가지 기능 (c ++)이있는 언어로 부여되면 둘 중 하나를 제거하여 선택적 인수를 구현할 수 있기 때문에 아무런 영향을 미치지 않습니다.


Haskell은 연산자 오버로드가 구문 설탕이 아닌 이유에 대한 좋은 예이지만 패턴 일치로 대수 데이터 형식을 해체하는 것이 더 좋은 예라고 생각합니다 (패턴 일치 없이는 불가능한 것).
11684

@ 11684 : 예를 들어 줄 수 있습니까? 나는 솔직히 Haskell을 전혀 몰랐지만 (YouTube의 컴퓨터 애호가에서) 그 훌륭한 예를 보았을 때 패턴 일치가 굉장히 우아하다는 것을 알았습니다.
slebetman

주어진 데이터 타입 data PaymentInfo = CashOnDelivery | Adress String | UserInvoice CustomerInfo은 타입 생성자에서 패턴 일치를 할 수 있습니다.
11684

이와 같이 : getPayment :: PaymentInfo -> a getPayment CashOnDelivery = error "Should have been paid already" getPayment (Adress addr) = -- code to notify administration to send a bill getPayment (UserInvoice cust) = --whatever. I took the data type from a Haskell tutorial and have no idea what an invoice is. 이 의견이 다소 이해할 수 있기를 바랍니다.
11684

0

컴파일 타임에 명확한 함수 호출이 필요하기 때문에 대부분의 언어 (적어도 내가 아는 모든 것)에서는 단순한 구문 설탕이라고 생각합니다. 그리고 컴파일러는 단순히 함수 호출을 올바른 구현 서명에 대한 명시 적 포인터로 대체합니다.

자바 예제 :

String s; int i;
mangle(s);  // Translates to CALL ('mangle':LString):(s)
mangle(i);  // Translates to CALL ('mangle':Lint):(i)

결국 결과는 오버로드 된 함수 mangle을 mangle_String 및 mangle_int로 대체하여 검색 및 바꾸기가있는 간단한 컴파일러 매크로로 완전히 대체 될 수 있습니다. 인수 목록은 최종 함수 식별자의 일부이기 때문에 실제로 발생합니다-> 및 그러므로 그것은 단지 구문 설탕입니다.

이제 객체의 재정의 된 메소드와 같이 런타임에 함수가 실제로 결정되는 언어가 있다면 이것은 다릅니다. 그러나 method.overloading은 애매 모호한 경향이 있기 때문에 컴파일러가 해결할 수 없으며 명시 적 캐스트로 프로그래머가 처리 해야하는 언어가 있다고 생각하지 않습니다. 런타임에는 수행 할 수 없습니다.


0

Java 유형에서는 정보가 컴파일되고 어떤 오버로드가 컴파일 시간에 결정됩니다.

다음은 sun.misc.UnsafeEclipse 클래스 파일 편집기에서 볼 수있는 (Atomics 유틸리티 )의 스 니펫입니다 .

  // Method descriptor #143 (Ljava/lang/Object;I)I (deprecated)
  // Stack: 4, Locals: 3
  @java.lang.Deprecated
  public int getInt(java.lang.Object arg0, int arg1);
    0  aload_0 [this]
    1  aload_1 [arg0]
    2  iload_2 [arg1]
    3  i2l
    4  invokevirtual sun.misc.Unsafe.getInt(java.lang.Object, long) : int [231]
    7  ireturn
      Line numbers:
        [pc: 0, line: 213]

보시다시피 호출되는 메소드의 유형 정보 (라인 4)가 호출에 포함됩니다.

이것은 타입 정보를 취하는 자바 컴파일러를 만들 수 있다는 것을 의미합니다. 예를 들어 이러한 표기법을 사용하면 위의 소스는 다음과 같습니다.

@Deprecated
public in getInt(Object arg0, int arg1){
     return getInt$(Object,long)(arg0, arg1);
}

길게 캐스트는 선택 사항입니다.

다른 정적으로 형식화 된 컴파일 된 언어에서는 컴파일러가 형식에 따라 호출 할 오버로드를 결정하고이를 바인딩 / 호출에 포함시키는 유사한 설정을 볼 수 있습니다.

유형 정보가 포함되지 않은 오버로드 된 함수를 작성하려고하면 링커가 불평하는 C 동적 라이브러리는 예외입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.