타입 추론이 유용한 이유는 무엇입니까? 이것에 대한 오래된 알고리즘이 있습니다. 그러나 실제

코드를 작성하는 것보다 코드를 더 자주 읽으며 산업용 소프트웨어를 사용하는 대부분의 프로그래머 가이 작업을 수행한다고 가정합니다. 내가 생각하는 형식 유추의 장점은 덜 장황하고 작성된 코드가 적다는 것입니다. 그러나 반면에 코드를 더 자주 읽는다면 읽을 수있는 코드가 필요할 것입니다.

컴파일러는 형식을 유추합니다. 이것에 대한 오래된 알고리즘이 있습니다. 그러나 실제 질문은 프로그래머가 코드를 읽을 때 변수 유형을 추론하려는 이유는 무엇입니까? 유형이 무엇인지 생각하는 것보다 유형을 읽는 것이 더 빠르지 않습니까?

편집 : 결론적으로 그것이 왜 유용한 지 이해합니다. 그러나 언어 기능 범주에서 연산자 과부하가있는 버킷에서 볼 수 있습니다. 경우에 따라 유용하지만 악용되면 가독성에 영향을 미칩니다.



답변

Java를 살펴 보자. Java는 유추 된 유형의 변수를 가질 수 없습니다. 이것은 타입이 무엇인지 인간 독자에게 완전히 명백하더라도, 타입을 자주 철자해야 함을 의미합니다.

int x = 42;  // yes I see it's an int, because it's a bloody integer literal!

// Why the hell do I have to spell the name twice?
SomeObjectFactory<OtherObject> obj = new SomeObjectFactory<>();

때로는 전체 유형을 설명하는 것이 성가시다.

// this code walks through all entries in an "(int, int) -> SomeObject" table
// represented as two nested maps
// Why are there more types than actual code?
for (Map.Entry<Integer, Map<Integer, SomeObject<SomeObject, T>>> row : table.entrySet()) {
    Integer rowKey = entry.getKey();
    Map<Integer, SomeObject<SomeObject, T>> rowValue = entry.getValue();
    for (Map.Entry<Integer, SomeObject<SomeObject, T>> col : rowValue.entrySet()) {
        Integer colKey = col.getKey();
        SomeObject<SomeObject, T> colValue = col.getValue();
        doSomethingWith<SomeObject<SomeObject, T>>(rowKey, colKey, colValue);
    }
}

이 장황한 정적 타이핑은 프로그래머를 방해합니다. 대부분의 타입 주석은 우리가 이미 알고있는 것에 대한 반복적 인 라인 필러, 내용이없는 역류입니다. 그러나 버그를 발견하는 데 실제로 도움이 될 수 있으므로 정적 타이핑을 좋아하므로 동적 타이핑을 사용하는 것이 항상 좋은 대답은 아닙니다. 유형 유추는 두 세계에서 가장 좋습니다. 관련이없는 유형은 생략 할 수 있지만 여전히 내 프로그램 (유형)이 체크 아웃되어 있는지 확인하십시오.

형식 유추는 로컬 변수에 실제로 유용하지만 명확하게 문서화해야하는 공개 API에는 사용해서는 안됩니다. 때로는 유형이 코드에서 일어나는 일을 이해하는 데 매우 중요합니다. 이런 경우 타입 추론에만 의존하는 것은 어리석은 일입니다.

형식 유추를 지원하는 많은 언어가 있습니다. 예를 들면 다음과 같습니다.

  • C ++. auto키워드 트리거는 추론을 입력합니다. 그것 없이는 람다 또는 컨테이너의 항목에 대한 유형을 철자하는 것은 지옥이 될 것입니다.

  • 씨#. 을 사용하여 변수를 선언 var하면 제한된 형식의 형식 유추를 트리거 할 수 있습니다 . 유형 유추를 원하는 대부분의 경우를 관리합니다. 특정 장소에서는 유형을 완전히 생략 할 수 있습니다 (예 : 람다).

  • Haskell 및 ML 제품군의 모든 언어 여기에 사용 된 특정 유형의 유추 유형은 상당히 강력하지만 여전히 함수에 대한 유형 주석이 표시되는 두 가지 이유가 있습니다. 첫 번째는 문서이고 두 번째는 유형 유추가 실제로 예상 한 유형을 찾은 확인입니다. 불일치가있는 경우 일종의 버그가있을 수 있습니다.


답변

코드가 작성된 것보다 훨씬 자주 읽힌다는 것은 사실입니다. 그러나 읽기에도 시간이 걸리고 두 화면의 코드는 한 화면의 코드보다 탐색 및 읽기가 더 어렵 기 때문에 가장 유용한 정보 / 읽기 노력 비율을 우선 순위로 정해야합니다. 이것은 일반적인 UX 원칙입니다. 한 번에 너무 많은 정보가 압도되어 실제로 인터페이스의 효율성을 떨어 뜨립니다.

그리고 정확한 유형 중요 하지 않은 것은 종종 내 경험입니다 . 분명히 당신이 때때로 둥지 표현 : , , . 이들 각각에는 유형이 기록되지 않은 값으로 평가되는 하위 표현식이 포함됩니다. 그러나 그들은 완전히 명확합니다. 컨텍스트에서 파악하기가 충분히 쉽기 때문에 유형을 지정하지 않은 상태로 두는 것이 좋습니다. 데이터 흐름을 이해하지 못하면 데이터 흐름을 이해하고 귀중한 화면과 단기 메모리 공간을 확보해야합니다.x + y * zmonkey.eat(bananas.get(i))factory.makeCar().drive()

내일이없는 것처럼 표현식을 중첩하지 않는 한 가지 이유는 줄이 길어지고 값의 흐름이 명확하지 않기 때문입니다. 임시 변수를 도입하면 도움이되며 순서를 부과하고 부분 결과에 이름을 부여합니다. 그러나 이러한 측면에서 이점을 얻는 모든 것이 유형을 설명하는 것으로부터 이점을 얻는 것은 아닙니다.

user = db.get_poster(request.post['answer'])
name = db.get_display_name(user)

user엔터티 개체, 정수, 문자열 또는 다른 것이 중요합니까 ? 대부분의 경우 사용자가 HTTP 요청에서 왔으며 응답의 오른쪽 하단에 표시 할 이름을 가져 오는 데 사용된다는 것을 아는 것으로 충분하지 않습니다.

이 때 않는 문제, 저자는 유형을 작성하는 무료입니다. 이것은 책임감있게 사용해야하는 자유이지만 가독성 (변수 및 함수 이름, 형식, API 디자인, 공백)을 향상시킬 수있는 다른 모든 경우에도 마찬가지입니다. 실제로 Haskell과 ML의 규칙 ( 추가 노력없이 모든 것을 유추 할 수있는 경우)은 비 로컬 함수 함수의 유형과 적절한 경우마다 로컬 변수 및 함수를 작성하는 것입니다. 초보자 만 모든 유형을 유추 할 수 있습니다.


답변

타입 추론은 매우 중요하며 모든 현대 언어로 지원되어야한다고 생각합니다. 우리는 모두 IDE에서 개발하며 추론 된 유형을 알고 싶을 때 많은 도움을 줄 수 vi있습니다. 예를 들어 Java의 장황함과 의식 코드를 생각해보십시오.

  Map<String,HashMap<String,String>> map = getMap();

그러나 당신은 내 IDE가 나에게 도움이된다고 말할 수 있습니다. 유효한 포인트 일 수 있습니다. 그러나 C # 익명 형식과 같은 형식 유추의 도움 없이는 일부 기능이 없을 수 있습니다.

 var person = new {Name="John Smith", Age = 105};

Linq는 Select예를 들어 형식 유추의 도움이 없다면 지금처럼 좋지 않을 것입니다.

  var result = list.Select(c=> new {Name = c.Name.ToUpper(), Age = c.DOB - CurrentDate});

이 익명 유형은 변수에 깔끔하게 추론됩니다.

나는 Scala당신의 요점이 여기에 적용된다고 생각하기 때문에 반환 유형에 대한 유형 유추를 싫어합니다. 우리가 API를 더 유창하게 사용할 수 있도록 함수가 무엇을 반환하는지 분명해야합니다.


답변

이것에 대한 대답은 정말 간단하다고 생각합니다. 중복 정보를 읽고 쓰는 것을 저장합니다. 특히 등호의 양쪽에 유형이있는 객체 지향 언어에서.

또한 정보가 중복되지 않을 때 사용하지 말아야 할 때를 알려줍니다.


답변

코드를 본다고 가정 해 봅시다.

someBigLongGenericType variableName = someBigLongGenericType.someFactoryMethod();

경우 someBigLongGenericType의 반환 형식에서 할당 할 someFactoryMethod, 유형이 정확히 일치하지 않는 경우 가능성을 코드를 읽는 사람은 예고하는 것, 어떻게 쉽게 통지 불일치를 한 사람은 의도적인지 아닌지 알 수 있을까?

추론을 허용함으로써, 언어는 코드를 읽는 사람에게 변수의 유형이 명시 적으로 언급 될 때 그 이유를 찾아야한다고 제안 할 수 있습니다. 이를 통해 코드를 읽는 사람들이 자신의 노력에 더 집중할 수 있습니다. 반대로, 유형이 지정 될 때 대부분의 시간이 유추 된 시간과 정확히 동일하게 발생하면 코드를 읽는 사람이 미묘하게 다른 시간을 알아 채기가 쉽지 않을 수 있습니다. .


답변

이미 많은 훌륭한 답변이 있음을 알았습니다. 그중 일부는 반복 될 것이지만 때로는 자신의 말로 물건을 넣고 싶어합니다. 나는 가장 친숙한 언어이기 때문에 C ++의 몇 가지 예를 언급 할 것입니다.

필요한 것은 결코 현명하지 않습니다. 다른 언어 기능을 실용화하려면 형식 유추가 필요합니다. C ++에서는 처리 할 수없는 유형을 가질 수 있습니다.

struct {
    double x, y;
} p0 = { 0.0, 0.0 };
// there is no name for the type of p0
auto p1 = p0;

C ++ 11은 말할 수없는 람다를 추가했습니다.

auto sq = [](int x) {
    return x * x;
};
// there is no name for the type of sq

형식 유추도 템플릿을 뒷받침합니다.

template <class x_t>
auto sq(x_t const& x)
{
    return x * x;
}
// x_t is not known until it is inferred from an expression
sq(2); // x_t is int
sq(2.0); // x_t is double

그러나 당신의 질문은 “프로그래머가 내가 코드를 읽을 때 변수의 타입을 추론하고 싶어하는 이유는 무엇입니까?”

유형 유추는 중복성을 제거합니다. 코드를 읽을 때 코드에 중복 정보를 저장하는 것이 더 빠르고 쉬울 있지만 중복성이 유용한 정보를 흐리게 할 수 있습니다 . 예를 들면 다음과 같습니다.

std::vector<int> v;
std::vector<int>::iterator i = v.begin();

C ++ 프로그래머가 i가 이터레이터임을 식별하기 위해 표준 라이브러리에 익숙하지 않으므로 i = v.begin()명시 적 유형 선언의 가치는 제한적입니다. 그 존재로 인해 더 중요한 세부 사항을 모호하게합니다 (예 : i벡터의 시작 부분을 가리킴). @amon의 정답은 중요한 세부 사항을 어둡게하는 자세한 표현의 예를 제공합니다. 반대로 형식 유추를 사용하면 중요한 세부 사항이 더 두드러집니다.

std::vector<int> v;
auto i = v.begin();

코드를 읽는 것이 중요하지만 충분하지 않지만 어느 시점에서 읽기를 중단하고 새 코드를 작성해야합니다. 코드의 중복성은 코드 수정 속도를 늦추고 어렵게 만듭니다. 예를 들어 다음 코드 조각이 있다고 가정 해보십시오.

std::vector<int> v;
std::vector<int>::iterator i = v.begin();

벡터의 값 유형을 변경하여 코드를 두 번 변경 해야하는 경우 :

std::vector<double> v;
std::vector<double>::iterator i = v.begin();

이 경우 코드를 두 곳에서 수정해야합니다. 원본 코드가 다음과 같은 형식 유추와 대조됩니다.

std::vector<int> v;
auto i = v.begin();

그리고 수정 된 코드 :

std::vector<double> v;
auto i = v.begin();

이제 한 줄의 코드 만 변경하면됩니다. 이것을 큰 프로그램으로 추정하면 형식 유추를 통해 편집기를 사용하는 것보다 훨씬 빠르게 변경 사항을 형식에 전파 할 수 있습니다.

코드 중복성으로 인해 버그가 발생할 수 있습니다. 코드가 동등하게 유지되는 두 가지 정보에 의존 할 때마다 실수의 가능성이 있습니다. 예를 들어,이 문장에서 두 유형 사이에 불일치가 있는데 이는 의도하지 않은 것입니다.

int pi = 3.14159;

중복성은 의도를 식별하기 어렵게 만듭니다. 경우에 따라 형식 유추가 명시 적 형식 지정보다 단순하기 때문에 읽고 이해하기가 더 쉽습니다. 코드 조각을 고려하십시오.

int y = sq(x);

sq(x)반환하는 경우 반환 유형 인지 또는 사용하는 문에 적합한 int지 여부 y는 확실하지 않습니다 . 더 이상 반환하지 않도록 다른 코드를 변경하면 해당 행에서만 유형을 업데이트 해야하는지 확실 하지 않습니다 . 동일한 코드와 대조하지만 형식 유추를 사용하십시오.intsq(x)ysq(x)inty

auto y = sq(x);

이것에서 의도는 명확하다 .에 y의해 반환 된 것과 같은 타입이어야한다 sq(x). 코드의 반환 형식을 변경하면 sq(x), 유형 y변경 사항이 자동으로 일치합니다.

C ++에서는 위의 예제가 형식 유추로 더 간단한 두 번째 이유가 있습니다. 유형 유추는 암시 적 유형 변환을 도입 할 수 없습니다. 의 반환 유형 sq(x)이 아닌 int경우 컴파일러는에 암시 적 변환을 자동으로 삽입합니다 int. 의 반환 유형 이을 sq(x)정의하는 유형 복합 유형 인 operator int()경우이 숨겨진 함수 호출은 임의로 복잡 할 수 있습니다.