주류 강력한 정적 OOP 언어가 프리미티브 상속을 방지하는 이유는 무엇입니까?


53

이것이 왜 OK이며 대부분 기대되는 이유는 다음과 같습니다.

abstract type Shape
{
   abstract number Area();
}

concrete type Triangle : Shape
{
   concrete number Area()
   {
      //...
   }
}

... 이것은 좋지 않으며 아무도 불평하지 않습니다.

concrete type Name : string
{
}

concrete type Index : int
{
}

concrete type Quantity : int
{
}

내 동기는 컴파일 타임 정확성 검증을 위해 유형 시스템의 사용을 최대화하고 있습니다.

추신 : 예, 저는 읽고 및 포장 작업 주위에 해키입니다.


1
의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
maple_shaft

이 질문에 비슷한 동기가 있었는데 흥미로울 것입니다.
default.kramer

"상속을 원하지 않는다"라는 아이디어를 확인하는 대답을 추가하려고했으며 , 특히 JIT 최적화를 통해 원하는 암시 적 또는 명시 적 캐스팅 (또는 실패)을 제공하는 등 래핑 매우 강력 하다는 것을 알았습니다. 어쨌든 거의 같은 성능을 얻지 만 그 대답에 링크 했습니다. :-) 나는 단지 추가 할 것입니다. 특히 단일 값만있는 경우 언어가 속성 / 메소드를 전달하는 데 필요한 상용구 코드를 줄이는 기능을 추가하면 좋을 것입니다.
Mark Hurd

답변:


83

Java 및 C #과 같은 언어를 생각한다고 가정합니까?

이러한 언어에서 (와 같은 int) 프리미티브 는 기본적으로 성능을 저하시킵니다. 객체의 모든 기능을 지원하지는 않지만 더 빠르고 오버 헤드가 적습니다.

객체가 상속을 지원하기 위해서는 각 인스턴스가 인스턴스가 어느 클래스인지 런타임에 "알아야"합니다. 그렇지 않으면 재정의 된 메서드를 런타임에 확인할 수 없습니다. 객체의 경우 인스턴스 데이터가 클래스 객체에 대한 포인터와 함께 메모리에 저장됩니다. 그러한 정보가 프리미티브 값들과 함께 저장되어야한다면, 메모리 요구량은 증가 할 것이다. 16 비트 정수 값에는 해당 값에 16 비트가 필요하며 클래스에 대한 포인터에는 32 또는 64 비트 메모리가 추가로 필요합니다.

메모리 오버 헤드 외에 산술 연산자와 같은 프리미티브의 일반적인 연산을 재정의 할 수도 있습니다. 하위 유형을 지정하지 않으면 같은 연산자 +를 간단한 기계 코드 명령어로 컴파일 할 수 있습니다. 재정의 될 수있는 경우 훨씬 비용이 많이 드는 작업 인 런타임에 메서드를 해결해야 합니다. C #에서 연산자 오버로드를 지원한다는 사실을 알고있을 수도 있지만 이는 동일하지 않습니다. 컴파일시 연산자 오버로드가 해결되므로 기본 런타임 패널티가 없습니다.

문자열은 프리미티브가 아니지만 메모리에서 표현되는 방식에서 여전히 "특별"합니다. 예를 들어 "interned"은 동일한 두 문자열 리터럴이 동일한 참조에 최적화 될 수 있음을 의미합니다. 문자열 인스턴스가 클래스를 추적 해야하는 경우에는 불가능합니다 (또는 최소한 덜 효과적입니다).

설명하는 것은 확실히 유용하지만 상속을 활용하지 않는 경우에도 기본 요소와 문자열을 사용할 때마다 성능 오버 헤드가 필요합니다.

스몰 토크 언어의 서브 클래 싱을 허용한다고 생각합니다. 그러나 Java가 설계되었을 때 스몰 토크는 너무 느리게 여겨졌 고 모든 것을 객체로 만드는 오버 헤드가 주요 이유 중 하나로 간주되었습니다. Java는 더 나은 성능을 얻기 위해 약간의 우아함과 개념적 순도를 희생했습니다.


12
@Den : string는 불변으로 동작하도록 설계되었으므로 봉인되었습니다. 문자열에서 상속받을 수 있으면 변경 가능한 문자열을 만들 수 있으므로 실제로 오류가 발생하기 쉽습니다. .NET 프레임 워크 자체를 포함한 수많은 코드는 부작용이없는 문자열에 의존합니다. : 여기를 참조하십시오, 당신에게 같은 이야기 quora.com/Why-String-class-in-C-is-a-sealed-class을
독 브라운

5
@DocBrown 이것은 Java 로도 String표시 되는 이유이기도합니다 final.
Dev

47
"Java가 설계 될 때 스몰 토크는 너무 느리게 여겨졌다 […]. Java는 더 나은 성능을 얻기 위해 우아함과 개념적 순도를 희생했습니다." – 아이러니하게도 물론 썬은 자체 JVM이 느리기 때문에 썬이 스몰 토크 VM 기술에 액세스하기 위해 스몰 토크 회사를 인수 할 때까지 실제로 그 성능을 얻지 못하고 약간 수정 된 스몰 토크 VM 인 핫스팟 JVM을 출시했습니다.
Jörg W Mittag

3
@underscore_d : C♯에 기본 유형 이 없음 을 명시 적으로 연결 한 답변 입니다. 물론, C♯의 구현이 존재하는 일부 플랫폼은 기본 유형을 갖거나 갖지 않을 수 있지만, C♯이 기본 유형을 갖는 것은 아닙니다. 예를 들어 CLI에 대해 Ruby 구현이 있으며 CLI에 기본 유형이 있지만 이것이 Ruby에 기본 유형이 있다는 의미는 아닙니다. 구현은 값 유형을 플랫폼의 기본 유형에 맵핑하여 값 유형을 구현하도록 선택하거나 선택하지 않을 수 있지만 이는 스펙의 일부가 아닌 개인용 내부 구현 세부 사항입니다.
Jörg W Mittag

10
그것은 모두 추상화에 관한 것입니다. 우리는 머리를 깨끗하게 유지해야합니다. 그렇지 않으면 말도 안됩니다. 예를 들어 : C♯는 .NET에서 구현됩니다. .NET은 Windows NT에서 구현됩니다. Windows NT는 x86에서 구현됩니다. x86은 이산화 규소에서 구현됩니다. SiO₂는 모래 일뿐입니다. 그래서 stringC in의 모래는 단지 모래입니까? 물론 stringC not의 in은 C says 스펙이 말하는 것입니다. 구현 방법은 관련이 없습니다. C♯의 기본 구현은 문자열을 바이트 배열로 구현하고, ECMAScript 구현은 이러한 문자열을 ECMAScript String등에 매핑합니다 .
Jörg W Mittag

20

어떤 언어가 제안하는 것은 서브 클래 싱이 아니라 서브 타이핑 입니다. 예를 들어 Ada를 사용하면 파생 유형 또는 하위 유형 을 만들 수 있습니다 . 에이다 프로그래밍 / 유형 시스템 섹션에서 모든 세부 사항을 이해하는 읽기 가치가있다. 대부분의 시간 범위 인 값의 범위를 제한 할 수 있습니다.

 type Angle is range -10 .. 10;
 type Hours is range 0 .. 23; 

명시 적으로 변환하면 두 유형을 모두 정수로 사용할 수 있습니다. 또한 범위가 구조적으로 동일한 경우에도 유형을 이름으로 확인하더라도 다른 위치 대신 사용할 수는 없습니다 .

 type Reference is Integer;
 type Count is Integer;

위의 유형은 동일한 범위의 값을 나타내더라도 호환되지 않습니다.

(하지만 Unchecked_Conversion을 사용할 수 있습니다. 내가 말한 사람들에게 말하지 마십시오)


2
사실, 나는 그것이 의미론에 관한 것이라고 생각합니다. 인덱스가 예상되는 수량을 사용하면 컴파일 타임 오류가 발생할 수 있습니다.
Marjan Venema

@MarjanVenema이 작업은 논리 오류를 잡기 위해 의도적으로 수행됩니다.
coredump

내 요점은 의미를 원하는 모든 경우가 아니라 범위가 필요하다는 것입니다. 그렇다면 type Index is -MAXINT..MAXINT;모든 정수가 유효하기 때문에 어떻게 든 나를 위해 아무것도하지 않을 것입니까? 그렇다면 검사 된 모든 것이 범위이면 인덱스에 각도를 전달하면 어떤 종류의 오류가 발생합니까?
Marjan Venema

1
@MarjanVenema 두 번째 예에서 두 유형 모두 Integer의 하위 유형입니다. 그러나 Count를 허용하는 함수를 선언하면 형식 검사가 이름 동등성에 기반하기 때문에 Reference를 전달할 수 없습니다 . 이는 "체크 된 모든 것이 범위"입니다. 이것은 정수로 제한되지 않으며 열거 된 유형이나 레코드를 사용할 수 있습니다. ( archive.adaic.com/standards/83rat/html/ratl-04-03.html )
coredump

1
@Marjan 태그 유형이 매우 강력한 이유 중 하나는 Eric Lippert의 OCaml에서 Zorg 구현에 관한 시리즈에서 찾을 수 있습니다 . 이렇게하면 컴파일러가 많은 버그를 잡을 수 있습니다. 반면에 암시 적으로 유형을 변환 할 수 있으면 기능이 쓸모없는 것처럼 보입니다. PersonAge 유형을 PersonId 유형에 할당하는 것은 의미 론적으로 의미가 없습니다. 둘 다 동일한 기본 유형을 갖기 때문입니다.
Voo

16

나는 이것이 X / Y 질문이라고 생각합니다. 질문에서 두드러진 점은 ...

내 동기는 컴파일 타임 정확성 검증을 위해 유형 시스템의 사용을 최대화하고 있습니다.

... 그리고 당신의 의견에서 정교하게 :

나는 암묵적으로 하나를 다른 것으로 대체하고 싶지 않습니다.

실례지만 실례지만 ... 이것이 목표라면 왜 지구상에서 상속에 대해 이야기하고 있습니까? 암시적인 대체 가능성은 마치 전체와 같습니다. 알다시피, Liskov 대체 원칙?

실제로 당신이 원하는 것처럼 보이는 것은 '강력한 typedef'의 개념입니다. 예를 들어 어떤 것이 ' int범위'와 표현의 관점에서 ' 그러나'int 그 반대의 상황으로 대체 될 수는 없습니다 . 이 용어에 대한 정보와 귀하가 선택한 언어가 호출 할 수있는 정보를 검색하는 것이 좋습니다. 다시 말하지만, 그것은 상속의 반대입니다.

그리고 X / Y 답변이 마음에 들지 않는 사람들에게는 LSP와 관련하여 제목에 여전히 대답 할 수 있다고 생각합니다. 그들은 매우 간단한 일을하기 때문에 원시적 인 형태의 원시적이며, 그것은 그들이 모두이다 . 그것들을 상속 받게하여 그들의 가능한 효과를 무한대로 만드는 것은 최악의 경우 치명적인 LSP 위반에 큰 놀라움을 줄 것입니다. Thales Pereira 가이 놀라운 의견을 인용하는 것을 신경 쓰지 않을 것이라고 낙관적으로 가정한다면 :

누군가가 Int로부터 상속받을 수 있다면 데이터베이스에 로그를 쓰고 URL을 열고 "int x = y + 2"(여기서 Y는 파생 클래스)와 같은 무고한 코드가 생길 수 있다는 추가 문제가 있습니다. 어떻게 든 엘비스를 부활시킵니다. 기본 유형은 안전하고 확실하게 정의 된 동작을 보장합니다.

누군가가 제정신의 언어로 원시적 유형을 본다면, 당연히 그것이 놀라지 않고 항상 아주 작은 일 하나만 할 것이라고 추정합니다. 기본 유형에는 상속 될 수 있는지 여부와 메소드를 재정의 할 수 있는지 여부를 나타내는 클래스 선언이 없습니다. 그것들이 사실이라면, 그것은 정말 놀랍습니다 (그리고 완전히 하위 호환성을 깨뜨 렸지 만, 왜 'X가 Y로 설계되지 않았는가'에 대한 역 답변입니다).

...하지만, 같은 음매 오리가 지적한 응답, 운영자들이 정말로 원하는 경우 유사하거나 동일한 정도로 자신을 혼동하는 사용자를 활성화 과부하, 그래서이 마지막 인수가 보유 여부를 의심의 허용 언어. 그리고 다른 사람들의 의견을 요약하는 것을 그만두겠습니다.


4

응용 프로그램 디자인에서 종종 바람직한 가상 디스패치 8을 사용하여 상속을 허용하려면 런타임 유형 정보가 필요합니다. 모든 객체에 대해 객체 유형과 관련된 일부 데이터를 저장해야합니다. 정의에 따라 프리미티브에는이 정보가 없습니다.

프리미티브를 특징으로하는 C #과 Java의 두 가지 (관리, VM에서 실행) 주류 OOP 언어가 있습니다. 다른 많은 언어 들은 처음에는 기본 요소를 가지고 있지 않거나 , 그것들을 허용 / 사용하기 위해 비슷한 추론을 사용합니다.

프리미티브는 성능을 저하시킵니다. 각 객체에 대해 객체 헤더 (Java의 경우 일반적으로 64 비트 VM의 경우 2 * 8 바이트)와 필드 및 최종 패딩 (핫스팟의 경우 모든 객체의 배수)이 필요합니다. 8). 따라서 intas 객체는 4 바이트 (Java에서) 대신 24 바이트 이상의 메모리가 필요합니다.

따라서 기본 유형이 추가되어 성능이 향상되었습니다. 그들은 많은 것들을 더 쉽게 만듭니다. a + b둘 다 하위 유형 인 경우 무엇을 의미 int합니까? 올바른 추가를 선택하려면 어떤 종류의 dispathcing을 추가해야합니다. 이것은 가상 발송을 의미합니다. 추가를 위해 매우 간단한 opcode를 사용할 수있는 기능이 훨씬 더 빠르며 컴파일 타임 최적화가 가능합니다.

String또 다른 경우입니다. Java와 C # 모두 String객체입니다. 그러나 C #에서는 봉인되었으며 Java에서는 최종입니다. Java 및 C # 표준 라이브러리 모두 Strings를 변경할 수 없기 때문에 서브 클래 싱하면 이러한 불변성을 깰 수 있습니다.

Java의 경우 VM은 인턴 문자열을 "풀링"하여 성능을 향상시킬 수 있습니다. 이것은 문자열이 불변 인 경우에만 작동합니다.

또한 기본 유형을 서브 클래 싱 할 필요가 거의 없습니다 . 프리미티브가 서브 클래 싱 될 수없는 한, 수학이 우리에게 알려주는 많은 깔끔한 것들이 있습니다. 예를 들어, 덧셈이 교환적이고 연관성이 있는지 확인할 수 있습니다. 그것은 정수의 수학적 정의가 우리에게 알려주는 것입니다. 또한 많은 경우에 유도를 통해 루프를 통해 불변량을 쉽게 처리 할 수 ​​있습니다. 의 서브 클래 싱을 허용하면 int더 이상 특정 속성이 유지되는 것을 보장 할 수 없기 때문에 수학에서 제공하는 도구를 잃게됩니다. 따라서 기본 유형을 서브 클래스 화 할 수 없는 기능 은 실제로 좋은 것이라고 말합니다 . 누군가가 부술 수있는 일이 적고 컴파일러는 종종 특정 최적화를 수행 할 수 있음을 증명할 수 있습니다.


1
이 답변은 심연입니다 ... 좁습니다. to allow inheritance, one needs runtime type information.그릇된. For every object, some data regarding the type of the object has to be stored.그릇된. There are two mainstream OOP languages that feature primitives: C# and Java.C ++은 현재 주류가 아닌가? 런타임 유형 정보 C ++ 용어 이므로 반박으로 사용하겠습니다 . 그것은 절대적으로 사용하지 않는 한 필요하지 않은 것 dynamic_cast또는 typeid. 그리고 경우에도 클래스가있는 경우 RTTI의에, 상속은 단지 공간을 차지 virtual방법의 클래스마다 테이블 인스턴스에 지적해야되는 방법
underscore_d

1
C ++의 상속은 VM에서 실행되는 언어와는 전혀 다른 방식으로 작동합니다. 가상 디스패치에는 RTTI가 필요합니다. RTTI는 원래 C ++의 일부가 아니 었습니다. 가상 디스패치가없는 상속은 매우 제한적이며 가상 디스패치의 상속과 비교해야하는지 확실하지 않습니다. 또한 "오브젝트"의 개념은 C ++에서 매우 다르며 C # 또는 Java에서 다릅니다. 네 말이 맞아, 내가 더 잘 말할 수있는 것이 있지만, tbh는 모든 관련 포인트에 빠르게 들어가서 언어 디자인에 관한 책을 써야합니다.
Polygnome

3
또한 C ++에서 "가상 디스패치에는 RTTI가 필요"하지 않습니다. 다시 말하지만, 단지 dynamic_casttypeinfo이 필요합니다. 가상 디스패치는 객체의 구체적인 클래스에 대한 vtable에 대한 포인터를 사용하여 실제로 구현되므로 올바른 함수를 호출 할 수 있지만 RTTI 고유의 유형 및 관계에 대한 세부 사항은 필요하지 않습니다. 컴파일러는 객체의 클래스가 다형성인지, 그렇다면 인스턴스의 vptr이 무엇인지 알아야합니다. 를 사용하여 사실상 디스패치 된 클래스를 간단하게 컴파일 할 수 있습니다 -fno-rtti.
underscore_d

2
RTTI는 가상 디스패치가 필요합니다. 말 그대로 -C ++는 dynamic_cast가상 디스패치 가없는 클래스를 허용하지 않습니다 . 구현 이유는 RTTI가 일반적으로 vtable의 숨겨진 멤버로 구현되기 때문입니다.
MSalters

1
@MilesRout C ++에는 언어가 OOP에 필요한 모든 것, 적어도 다소 새로운 표준이 있습니다. 오래된 C ++ 표준에는 OOP 언어에 필요한 것이 부족하지만 심지어는 확장이 가능하다고 주장 할 수도 있습니다. C ++은 어떤 것들에 대해보다 직접적이고 낮은 수준의 제어를 허용하기 때문에 높은 수준의 OOP 언어 는 아니지만 OOP를 허용합니다. (여기서 추상화 측면에서 높은 수준 / 낮은 수준 , 관리되는 언어와 같은 다른 언어는 C ++보다 시스템을 더 많이 추상화하므로 추상화가 더 높습니다).
Polygnome

4

주류 강력한 정적 OOP 언어에서 하위 입력은 주로 형식을 확장하고 형식의 현재 메서드를 재정의하는 방법으로 간주됩니다.

이를 위해 '객체'에는 유형에 대한 포인터가 포함됩니다. 이는 오버 헤드입니다. Shape인스턴스 를 사용하는 메소드의 코드 는 올바른 Area()메소드를 호출 하기 전에 해당 인스턴스의 유형 정보에 액세스해야합니다 .

프리미티브는 단일 기계 언어 명령어로 변환 될 수있는 조작 만 허용하며 어떤 유형 정보도 포함하지 않습니다. 누군가가 서브 클래스를 만들 수 있도록 정수를 느리게 만들면 주류가되는 언어를 멈출만큼 매력적이지 않습니다.

이에 대한 답변은 다음과 같습니다.

주류 강력한 정적 OOP 언어가 프리미티브 상속을 방지하는 이유는 무엇입니까?

입니다 :

  • 거의 수요가 없었다
  • 그리고 언어를 너무 느리게 만들었을 것입니다
  • 서브 타이핑은 기본적으로 더 나은 (사용자 정의) 정적 유형 검사를 얻는 것이 아니라 유형을 확장하는 방법으로 여겨졌습니다.

그러나 'type'이외의 변수 속성을 기반으로 정적 검사를 허용하는 언어를 가져 오기 시작했습니다. 예를 들어 F #에는 "dimension"및 "unit"이 있으므로 영역에 길이를 추가 할 수 없습니다 .

유형이하는 일을 바꾸거나 바꾸지 않고 정적 유형 검사에 도움을주는 '사용자 정의 유형'을 허용하는 언어도 있습니다. 코어 덤프의 답변을 참조하십시오.


불행히도 이름이 잘못 지정되었지만 F # 측정 단위는 유용한 기능입니다. 또한 컴파일 시간만이므로 컴파일 된 NuGet 패키지를 사용할 때 유용하지 않습니다. 그래도 올바른 방향입니다.
Den

"치수"가 " '유형'이외의 속성"이 아니라 익숙한 유형보다 더 다양한 유형일뿐입니다.
porglezomp

3

여기에 뭔가가 있는지 잘 모르겠지만 대답은 간단합니다.

  1. 프리미티브의 정의는 다음과 같습니다. 프리미티브 값은 객체가 아니며, 프리미티브 유형은 객체 유형이 아니며, 프리미티브는 객체 시스템의 일부가 아닙니다.
  2. 상속은 객체 시스템의 기능입니다.
  3. Ergo, 프리미티브 상속에 참여할 수 없습니다 .

AFAIK (Java 및 C ++)의 기본을 가진 두 개의 강력한 정적 OOP 언어가 실제로 있습니다 . (실제로, 나는 후자에 대해 확신조차하지 못하고 C ++에 대해 많이 알지 못하며 검색 할 때 발견 한 것이 혼란 스럽습니다.)

C ++에서 프리미티브는 기본적으로 C에서 상속 된 (pun 의도 된) 레거시입니다. 따라서 C에는 객체 시스템이나 상속이 없기 때문에 객체 시스템에 참여하지 않습니다.

Java에서 프리미티브는 성능 향상을위한 잘못된 시도의 결과입니다. 프리미티브는 시스템에서 유일한 값 유형이며, 실제로 Java로 값 유형을 작성하는 것은 불가능하며 객체가 값 유형이 될 수 없습니다. 그래서, 떨어져 심지어 원시 객체 시스템에 참여하지 않으며, 따라서 "상속"의 생각도 이해가되지 않는다는 사실에서 경우에 당신이 그들로부터 상속 수, 당신 "은을 유지 할 수 없을 것입니다 가치 "". 이 예 C♯ 다른 않습니다 값 유형 (이 struct그럼에도 불구하고 개체를).

또 다른 것은 상속 할 수없는 것이 실제로는 프리미티브에 고유하지 않다는 것입니다. C♯에서 structs는 s를 암시 적으로 상속 System.Object하고 구현할 수 interface있지만 classes 또는 structs 에서 상속하거나 상속 할 수는 없습니다 . 또한 sealed classes는 상속 될 수 없습니다. Java에서는 final classes를 상속 할 수 없습니다.

tl; dr :

주류 강력한 정적 OOP 언어가 프리미티브 상속을 방지하는 이유는 무엇입니까?

  1. 프리미티브는 객체 시스템의 일부가 아닙니다 (정의상, 프리미티브가 아닌 경우). 상속에 대한 아이디어는 객체 시스템에 연결되어 있으며, 인체 프리미티브 상속은 용어의 모순입니다.
  2. 프리미티브는 고유하지 않으며 다른 많은 유형도 상속 할 수 없습니다 ( final또는 sealedJava 또는 C♯, structC♯, scala, case classes)

3
흠 ... 나는 그것이 "C Sharp"로 발음된다는 것을 알고 있지만, ehm
Mr Lister

나는 당신이 C ++ 측면에서 착각했다고 생각합니다. 순수한 OO 언어가 아닙니다. 기본적으로 클래스 메소드는 not virtual이므로 LSP를 준수하지 않습니다. 예를 들어 std::string, 원시적 인 것은 아니지만 다른 가치처럼 행동합니다. 이러한 가치 의미론은 매우 일반적이며 C ++의 전체 STL 부분에서는이를 가정합니다.
MSalters

2
'Java에서 프리미티브는 성능 향상을위한 잘못된 시도의 결과입니다.' 사용자 확장 가능한 객체 유형으로 프리미티브를 구현할 때의 성능 저하의 크기에 대해 전혀 모른다고 생각합니다. 자바에서의 결정은 의도적이며 잘 확립되어 있습니다. int사용할 때마다 메모리를 할당해야한다고 상상해보십시오 . 각 할당은 100ns 정도의 가비지 수집 오버 헤드를가집니다. 두 개의 기본 요소를 추가하여 소비 된 단일 CPU주기와 비교하십시오 int. 언어 디자이너가 달리 결정한 경우 Java 코드가 크롤링됩니다.
cmaster

1
@cmaster : 스칼라에는 프리미티브가 없으며 수치 성능은 Java와 정확히 동일합니다. 정수를 JVM 프리미티브로 컴파일하기 때문에 int정확히 동일하게 수행됩니다. (Scala-native는 원시 머신 레지스터로 컴파일하고 Scala.js는 원시 ECMAScript로 컴파일합니다 Number.) Ruby에는 원시가 없지만 YARV 및 Rubinius는 정수를 원시 머신 정수로 컴파일하고 JRuby는이를 JVM 프리미티브로 컴파일합니다 long. 거의 모든 Lisp, Smalltalk 또는 Ruby 구현은 VM에서 프리미티브 사용합니다 . 그것이 바로 성능 최적화입니다.
Jörg W Mittag

1
… 언어가 아닌 컴파일러에 속합니다.
Jörg W Mittag

2

"Effective Java"의 Joshua Bloch는 상속을 위해 명시 적으로 디자인하거나이를 금지 할 것을 권장합니다. 프리미티브 클래스는 불변으로 설계되고 상속을 허용하여 서브 클래스에서 변경 될 수 있으므로 Liskov 원칙 을 위반할 수 있으므로 많은 버그의 원인이 될 수 있으므로 상속 용으로 설계되지 않았습니다 .

어쨌든, 왜 해키 해결 방법? 상속보다 구성을 선호해야합니다. 그 이유가 당신이 지적한 것보다 성능이고 질문에 대한 대답은 기능 추가의 모든 다른 측면을 분석하는 데 시간이 걸리기 때문에 모든 기능을 Java에 넣을 수 없다는 것입니다. 예를 들어 Java에는 1.5 이전에 Generics가 없었습니다.

인내심 이 많으면 Java에 가치 클래스를 추가하여 성능을 높이는 동시에 가치를 높일 수있는 가치 클래스를 만들 수 있는 계획 이 있기 때문에 운이 좋을 것입니다.


2

추상 수준에서는 디자인하려는 언어로 원하는 것을 포함 할 수 있습니다.

구현 수준에서는 이러한 것들 중 일부는 구현하기가 더 쉽고, 일부는 복잡하고, 일부는 빨라질 수 있으며, 일부는 느려질 수밖에 없습니다. 이를 설명하기 위해 설계자들은 종종 어려운 결정과 타협을해야합니다.

구현 수준에서 변수에 액세스하는 가장 빠른 방법 중 하나는 주소를 찾고 해당 주소의 내용을로드하는 것입니다. 대부분의 CPU에는 주소에서 데이터를로드하기위한 특정 명령어가 있으며 이러한 명령어는 일반적으로로드해야하는 바이트 수 (1, 2, 4, 8 등)와로드 할 데이터를 배치 할 위치 (단일 레지스터, 레지스터)를 알아야합니다. 쌍, 확장 레지스터, 기타 메모리 등). 변수의 크기를 알면 컴파일러는 해당 변수의 사용법을 위해 어떤 명령어를 생성해야하는지 정확하게 알 수 있습니다. 변수의 크기를 알지 못하면 컴파일러는 더 복잡하고 느린 것에 의존해야합니다.

추상 수준에서 하위 유형 지정 지점은 동일하거나 더 일반적인 유형이 필요한 한 유형의 인스턴스를 사용할 수 있어야합니다. 다시 말해, 정확히 이것이 무엇인지 미리 알지 못하고 특정 유형의 객체 또는 더 파생 된 것을 기대하는 코드를 작성할 수 있습니다. 또한 파생 형식이 많을수록 더 많은 데이터 멤버를 추가 할 수 있으므로 파생 형식은 반드시 기본 형식과 동일한 메모리 요구 사항을 가질 필요는 없습니다.

구현 수준에서는 미리 결정된 크기의 변수가 알 수없는 크기의 인스턴스를 보유하고 일반적으로 효율적인 호출 방식으로 액세스 할 수있는 간단한 방법은 없습니다. 그러나 물건을 조금 움직이고 변수를 사용하여 객체를 저장하는 것이 아니라 객체를 식별하고 객체를 다른 곳에 저장하는 방법이 있습니다. 그러한 방법은 참조 (예 : 메모리 주소)입니다. 즉, 정보를 통해 객체를 찾을 수있는 한 변수가 특정 크기의 고정 크기 정보 만 보유하면되는 추가 수준의 간접적 수준입니다. 이를 위해서는 주소 (고정 크기) 만로드하면 알 수있는 오프셋에 더 많은 데이터가 있어도 유효한 것으로 알려진 개체의 오프셋을 사용하여 평소대로 작업 할 수 있습니다. 우리는 그렇게 할 수 있기 때문에

추상 수준에서,이 방법은 당신이 (A 참조) 저장할 수 있습니다 stringobject그것을 만드는 정보를 잃지 않고 변수를 string. 모든 유형이 이와 같이 작동하는 것이 좋으며 여러 측면에서 우아하다고 말할 수도 있습니다.

그럼에도 불구하고 구현 수준에서 추가 수준의 간접 지침에는 더 ​​많은 명령이 포함되며 대부분의 아키텍처에서는 개체에 대한 각 액세스가 다소 느려집니다. 언어에 여분의 간접 레벨 (참조)이없는 일반적으로 사용되는 유형을 포함 시키면 컴파일러가 프로그램에서 더 많은 성능을 끌어낼 수 있습니다. 그러나 해당 수준의 간접 성을 제거함으로써 컴파일러는 더 이상 메모리에 안전한 방식으로 하위 유형을 지정할 수 없습니다. 유형에 더 많은 데이터 멤버를 추가하고 더 일반적인 유형에 지정하면 대상 변수에 할당 된 공간에 맞지 않는 추가 데이터 멤버가 분리되기 때문입니다.


1

일반적으로

클래스가 추상적 인 경우 (유 : 상자가있는 상자) "구멍을 채우는"것은 괜찮습니다 (따라서 사용할 수 있어야합니다!). 따라서 우리는 추상 클래스를 서브 클래스 화합니다.

클래스가 구체적이라면 (유 : 상자 가득 참), 가득 차면 가득 차 있기 때문에 기존 내용을 바꾸는 것은 좋지 않습니다. 상자 안에 무언가를 더 넣을 공간이 없기 때문에 구체적인 클래스를 서브 클래스 화해서는 안됩니다.

프리미티브

프리미티브는 의도적으로 구체적인 클래스입니다. 그것들은 잘 알려져 있고 완전히 정의 된 것을 나타냅니다 (나는 추상적 인 것을 가진 원시적 유형을 본 적이 없으며, 더 이상 원시적 인 것이 아닙니다) 시스템을 통해 널리 사용됩니다. 프리미티브 유형의 서브 클래스를 허용하고 프리미티브의 설계된 동작에 의존하는 다른 사용자에게 자신의 구현을 제공하면 많은 부작용과 큰 손상을 초래할 수 있습니다!



링크는 흥미로운 디자인 의견입니다. 나를 위해 더 많은 생각이 필요합니다.
Den

1

일반적으로 상속은 원하는 의미론이 아닙니다. 프리미티브가 예상되는 곳에서는 특수 유형을 대체 할 수 없기 때문입니다. 예제에서 빌리려면 Quantity + Index의미 적으로 의미가 없으므로 상속 관계는 잘못된 관계입니다.

그러나 여러 언어에는 설명하려는 관계의 종류를 나타내는 값 유형 개념이 있습니다. 스칼라 가 한 예입니다. 값 유형은 기본 표현으로 기본 요소를 사용하지만 외부에서 다른 클래스 ID 및 조작을 갖습니다. 프리미티브 유형을 확장하는 효과가 있지만 상속 관계 대신 컴포지션에 가깝습니다.

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