프로그래밍 언어에서 바텀 타입을 사용해야하는 이유가 있습니까?


49

하단 유형은 주로 수학 유형 이론에 나타나는 구성입니다. 빈 유형이라고도합니다. 값이없는 유형이지만 모든 유형의 하위 유형입니다.

함수의 반환 유형이 맨 아래 유형이면 반환되지 않습니다. 기간. 영원히 반복되거나 예외가 발생할 수 있습니다.

프로그래밍 언어에서이 이상한 유형을 갖는 점은 무엇입니까? 흔하지는 않지만 Scala 및 Lisp와 같은 일부에는 존재합니다.


2
@SargeBorsch : 확실합니까? 물론 C에서는 명시 적으로 void데이터를 정의 할 수 없습니다 .
Basile Starynkevitch

3
@BasileStarynkevitch 유형의 값이 없으며 void단위 유형에는 하나의 값이 있어야합니다. 또한 지적했듯이 type의 값을 선언 할 수도 없습니다. void즉, 유형이 아니라 언어의 특수한 경우입니다.
Sarge Borsch

2
그렇습니다. 특히 포인터와 함수 포인터 유형이 작성되는 방식에서 C는 기괴합니다. 그러나 voidJava에서는 거의 동일합니다. 실제로 유형이 아니며 값을 가질 수 없습니다.
Sarge Borsch

3
하한 유형을 가진 언어의 의미론에서 하한 유형은 값이없는 것으로 간주되는 것이 아니라 하한 값인 하나의 값을 갖는 것으로 간주됩니다. 하한값은 모든 유형의 값이므로 하한값은 모든 유형의 하위 유형이 될 수 있습니다.
Theodore Norvell

4
@BasileStarynkevitch Common Lisp에는 nil 유형 이 있으며 값이 없습니다. 또한 단위 유형 인 기호 (일명, 기호) 만 있는 널 유형 도 있습니다 . nil()
Joshua Taylor

답변:


33

간단한 예제를 보겠다 : C ++ vs Rust.

C ++ 11에서 예외를 발생시키는 데 사용되는 함수는 다음과 같습니다.

[[noreturn]] void ThrowException(char const* message,
                                 char const* file,
                                 int line,
                                 char const* function);

그리고 여기 Rust에 해당합니다 :

fn formatted_panic(message: &str, file: &str, line: isize, function: &str) -> !;

순전히 구문상의 문제에서 Rust 구문은 더 합리적입니다. C ++ 구문은 반환 되지 않도록 지정하더라도 반환 유형 을 지정합니다. 조금 이상합니다.

표준 메모에서 C ++ 구문은 C ++ 11 (맨 위에 고정)로만 나타 났지만 다양한 컴파일러가 한동안 다양한 확장 기능을 제공하여 다양한 방법을 인식하도록 타사 분석 도구를 프로그래밍해야했습니다. 이 속성을 쓸 수 있습니다. 표준화 된 것이 분명히 우수합니다.


이제 이점은?

함수가 반환되지 않는다는 사실은 다음에 유용 할 수 있습니다.

  • 최적화 : 코드를 정리하면 반환 할 수 없으며 레지스터를 복원 할 필요가 없으므로 레지스터를 저장할 필요가 없습니다.
  • 정적 분석 : 많은 잠재적 인 실행 경로를 제거합니다
  • 유지 보수성 : (정적 분석 참조, 그러나 인간에 의한)

6
voidC ++ 예제에서 반환 유형이 아닌 함수 유형을 정의합니다 (일부). 함수가 허용하는 값을 제한하지 않습니다 return. void로 변환 할 수있는 것 (아무것도 아닙니다). 함수 return가 뒤에 오는 경우 값이 없어야합니다. 함수의 전체 유형은입니다 void () (char const*, char const*, int, char const *). :-) char const대신에 +1 사용const char
Clearer

4
그렇다고해서 밑바닥 유형을 갖는 것이 더 의미가있는 것은 아니며, 함수가 언어의 일부로 돌아 왔는지 아닌지에 대한 주석을 달아야한다는 의미가 있습니다. 실제로, 여러 가지 이유로 함수가 반환되지 않을 수 있기 때문에 부작용에 따라 함수에 주석을 달는 비교적 최근의 개념과 같이 포괄적 인 용어를 사용하는 대신 어떤 방식 으로든 이유를 인코딩하는 것이 좋습니다.
GregRos

2
실제로 "반환하지 않음"과 "반환 유형 X"를 독립적으로 설정해야하는 이유가 있습니다.
중복 제거기

[[noreturn]] 구 또는 기능의 부가의 파?
Zaibis

1
[계속] 전반적으로, 나는 on의 장점에 대한 논의가 as의 구현으로서 자격이되는 것을 정의해야한다고; ( a → ⊥) ≤ ( ab )가 없는 유형 시스템이 ⊥ 의 유용한 구현 이라고 생각하지 않습니다 . 따라서 이런 의미에서 SysV x86-64 C ABI (다른 것들 중에서)는 ⊥ 구현을 허용하지 않습니다.
Alex Shpilkin

26

칼의 대답은 좋습니다. 다른 사람이 언급하지 않은 추가 용도는 다음과 같습니다. 유형

if E then A else B

유형의 A모든 값과 유형의 모든 값을 포함하는 유형이어야합니다 B. 의 유형 경우 B입니다 Nothing, 다음의 유형 if표현의 유형이 될 수 있습니다 A. 나는 종종 루틴을 선언합니다

def unreachable( s:String ) : Nothing = throw new AssertionError("Unreachable "+s) 

코드에 도달하지 않을 것이라고 말하십시오. 그 형태이기 때문에 Nothing, unreachable(s)지금 어떤에서 사용할 수 있습니다 if(더 자주) 또는 switch결과의 유형에 영향을주지 않고. 예를 들어

 val colour : Colour := switch state of
         BLACK_TO_MOVE: BLACK
         WHITE_TO_MOVE: WHITE
         default: unreachable("Bad state")

스칼라는 그러한 유형이 없습니다.

Nothing(Karl의 답변에서 언급 한) 또 다른 유스 케이스 는 List [Nothing]입니다. 각 멤버의 유형이 Nothing 인 목록 유형입니다. 따라서 빈 목록의 유형이 될 수 있습니다.

Nothing이러한 유스 케이스를 작동시키는 주요 특성은 스칼라에서는 값이 없지만 값이없는 것이 아니라 다른 모든 유형의 하위 유형이라는 것입니다.

모든 유형에 동일한 값이 포함 된 언어가 있다고 가정 해 봅시다 (). 이러한 언어에서 ()유일한 유형의 단위 유형은 모든 유형의 하위 유형이 될 수 있습니다. 그렇다고 OP가 의미하는 바에 따라 이것이 최하위 유형은 아닙니다. OP는 하위 유형에 값이 없음을 분명히했습니다. 그러나 모든 유형의 하위 유형 인 유형이므로 하위 유형과 거의 동일한 역할을 수행 할 수 있습니다.

하스켈은 약간 다르게 행동합니다. Haskell에서 값을 생성하지 않는 표현식은 유형 체계를 가질 수 있습니다 forall a.a. 이 유형 체계의 인스턴스는 다른 유형과 통합되므로 (표준) Haskell에 하위 유형 지정 개념이 없더라도 효과적으로 최하위 유형으로 작동합니다. 예를 들어, error표준 전주곡 의 함수에는 유형 체계가 forall a. [Char] -> a있습니다. 그래서 당신은 쓸 수 있습니다

if E then A else error ""

표현식의 유형은 A모든 표현식 의 유형과 동일 합니다 A.

Haskell의 빈 목록에는 형식 체계가 forall a. [a]있습니다. A유형이 목록 유형 인 표현식 인 경우

if E then A else []

와 유형이 같은 표현식입니다 A.


Haskell 의 유형 forall a . [a]과 유형의 차이점은 무엇입니까 [a]? Haskell 타입 표현식에서 타입 변수가 이미 보편적으로 정량화되지 않았습니까?
Giorgio

@Giorgio Haskell에서는 형식 체계를보고있는 것이 분명하다면 범용 정량화가 암시 적입니다. forall표준 Haskell 2010에서는 글을 쓸 수 없습니다 .이 내용은 Haskell 포럼이 아니며 일부 사람들은 Haskell의 규칙에 익숙하지 않을 수 있기 때문에 명시 적으로 수량을 작성했습니다. 따라서 forall a . [a]표준이 아니라는 것을 제외하고 는 차이 가 없습니다 [a].
Theodore Norvell

19

유형 은 두 가지 방식으로 모노 이드 를 형성하며 함께 반반지를 만듭니다 . 이것이 대수 데이터 타입 입니다. 유한 유형의 경우,이 반올림은 자연수 반올림 (0 포함)과 직접 관련되므로 유형에 가능한 값의 수를 계산합니다 ( "종료되지 않은 값"제외).

  • 맨 아래 유형 (내가 부르겠습니다 Vacuous)의 값은 0 입니다. .
  • 단위 유형에는 하나의 값이 있습니다. 유형과 단일 값을 모두 호출합니다 ().
  • 구성 (대부분의 프로그래밍 언어는 레코드 / 구조 / 공용 필드의 클래스를 통해 매우 직접적으로 지원됨)은 제품 운영입니다. 예를 들어, (Bool, Bool)네 개의 가능한 값, 즉이 (False,False), (False,True), (True,False)(True,True).
    단위 유형은 작성 조작의 식별 요소입니다. 예를 들어 ((), False)((), True)은 type의 유일한 값 ((), Bool)이므로이 유형은 Bool자체에 동형 입니다.
  • 대체 유형은 대부분의 언어에서 무시되지만 (OO 언어는 상속으로 지원합니다), 그다지 유용하지는 않습니다. 두가지 타입의 대체 AB기본적으로는 모든 값을 가지고 A, 플러스의 모든 값 B, 따라서 합계 타입 . 예를 들어, Either () Bool세 개의 값을 가지고, 나는 그들에게 전화 것 Left (), Right False하고 Right True.
    : 하단 유형은 합계의 아이덴티티 요소 Either Vacuous A단지 형식의 값을 Right a하기 때문에, Left ...(이해가되지 않습니다 Vacuous더 값이 없습니다).

이 monoids에서 흥미로운 점은 언어에 함수 를 도입 할 때 함수 를 형태로 사용하는 이러한 유형 의 범주monoidal 범주라는 것 입니다. 무엇보다도 이것은 응용 함수 펑터와 모나드 를 정의 할 수있게하며 , 이는 순수한 기능적인 용어 내에서 일반적인 계산 (부수 효과 등을 포함 할 수 있음)에 대한 탁월한 추상화로 판명됩니다.

이제 실제로 문제의 한 쪽 (작문 일조)에 대해 걱정할 수 있으며 실제로 하단 유형을 명시 적으로 필요로하지 않습니다. 예를 들어, Haskell조차도 오랫동안 표준 바닥 유형이 없었습니다. 이제는이라고 Void합니다.

그러나 전체 그림을 bicartesian closed category 로 생각하면 유형 시스템은 실제로 전체 람다 미적분학과 동일하므로 기본적으로 Turing-complete 언어로 가능한 모든 것을 완벽하게 추상화합니다. 임베디드 도메인 별 언어에 적합합니다. 예를 들어 전자 회로를 이런 식으로 직접 코딩 하는 프로젝트가 있습니다 .

물론 이것이 모든 이론가들의 일반적인 말도 안된다고 말할 수도 있습니다 . 훌륭한 프로그래머가되기 위해 카테고리 이론에 대해 전혀 알 필요는 없지만, 그렇게하면 코드에 대해 추론하고 불변을 유발하는 강력하고 엄청나게 일반적인 방법을 제공합니다.


mb21는이와 혼동하지 않도록주의하는 생각 나게 바닥 값 . Haskell과 같은 게으른 언어에서 모든 유형에는로 표시된 "값"이 포함됩니다 . 이것은 명시 적으로 전달할 수있는 구체적인 것이 아니라 함수가 영원히 반복되는 경우와 같이 "복귀"된 것입니다. Haskell의 Void타입 조차도 가장 낮은 값을 포함하고, 따라서 이름입니다. 그런 관점에서 Haskell의 하단 유형은 실제로 하나의 값을 가지며 단위 유형은 두 개의 값을 가지지 만 카테고리 이론에서는 일반적으로 무시됩니다.


"하단 타입 (이하 부름 Void)" 은 Haskell 의 모든 유형의 멤버 인 value 와 혼동하지 않아야합니다 . bottom
mb21 2016 년

18

영원히 반복되거나 예외가 발생할 수 있습니다.

그런 상황에서는 유용한 유형처럼 들리지만, 드물 긴합니다.

또한 Nothing(하단 유형의 스칼라 이름)에 값 List[Nothing]이 없어도 제한이 없으므로 빈 목록의 유형으로 유용합니다. 대부분의 언어는 빈 문자열 목록을 정수의 빈 목록과 다른 유형으로 만들어서이 문제를 해결하지만 빈 목록을 더 자세하게 작성하면 목록 지향 언어에서 큰 단점이됩니다.


12
“Haskell의 빈 목록은 형식 생성자입니다.”여기에 관련된 것은 다형성 이거나 오버로드 된 것입니다. 즉, 다른 유형의 빈 목록은 고유 한 값이지만 []모든 값을 나타내며 필요에 따라 특정 유형.
피터 LeFanu Lumsdaine

흥미롭게도 Haskell 인터프리터에서 빈 배열을 만들려고하면 매우 무기한 유형으로 매우 명확한 값을 얻습니다 [a]. 마찬가지로 :t Left 1수율 Num a => Either a b. 실제로이 표현을 평가하는 것은 다음과 같은 유형이 a아닌 다음 유형을 강제합니다 b.Either Integer b
John Dvorak

5
빈 목록은 생성자입니다. 약간 혼란스럽게도 관련된 유형 생성자는 이름이 같지만 빈 목록 자체는 유형이 아닌 값입니다 (글꼴 수준 목록도 있지만 완전히 다른 주제입니다). 모든 목록 유형에 대해 빈 목록을 작동시키는 부분 forall은 해당 유형에 포함 forall a. [a]됩니다. 에 대해 생각할 수있는 좋은 방법이 forall있지만 실제로 알아내는 데 시간이 걸립니다.
David

@PeterLeFanuLumsdaine 바로 타입 생성자가 의미하는 바입니다. 단지 종류가 다른 유형임을 의미합니다 *.
GregRos

2
Haskell []에서 형식 생성자이며 []빈 목록을 나타내는 식입니다. 그러나 이것이 "Haskell의 빈 목록은 유형 생성자"를 의미하지는 않습니다. 컨텍스트는 []유형으로 사용 되는지 또는 표현식으로 사용 되는지를 명확하게합니다 . 선언한다고 가정하자 data Foo x = Foo | Bar x (Foo x). 이제 Foo유형 생성자 또는 값으로 사용할 수 있지만 둘 다 동일한 이름을 선택한 경우가 있습니다.
Theodore Norvell

3

정적 분석에서 특정 코드 경로에 도달 할 수 없다는 사실을 문서화하는 것이 유용합니다. 예를 들어 C #으로 다음을 작성하는 경우 :

int F(int arg) {
 if (arg != 0)
  return arg + 1; //some computation
 else
  Assert(false); //this throws but the compiler does not know that
}
void Assert(bool cond) { if (!cond) throw ...; }

컴파일러는 F적어도 하나의 코드 경로에서 아무것도 반환하지 않는다고 불평합니다 . 경우 Assert로 표시했다 비 반환 컴파일러은 경고 할 필요가 없습니다 것입니다.


2

null모든 유형의 하위 유형은 어떤 언어에 널을 사용 하는지를 잘 정의하기 때문에 일부 언어에서는 맨 아래 유형이 있습니다 (무엇을 사용해야하는지에 null대한 공통된 주장을 피하면서 자체와 반환되는 함수라는 가벼운 모순에도 불구하고 bot).

또한 any -> bot디스패치 실패를 처리하기 위해 함수 유형 ( ) 에서 범용으로 사용할 수 있습니다.

또한 일부 언어에서는 실제로 bot오류로 해결 하여 사용자 정의 컴파일러 오류를 제공하는 데 사용할 수 있습니다.


11
아니요, 하단 유형은 단위 유형이 아닙니다. 바텀 타입은 전혀 값이 없으므로, 바텀 타입을 반환하는 함수는 반환하지 않아야합니다 (즉, 예외를 던지거나 무한정 반복)
Basile Starynkevitch

@BasileStarynkevitch-나는 단위 유형에 대해 이야기하고 있지 않습니다. 단위 유형 void은 공통 언어 (동일한 사용을 위해 약간 다른 의미가 있지만)로 매핑됩니다 null. 대부분의 언어가 널 유형을 맨 아래 유형으로 모델링하지 않는 것이 맞습니다.
Telastyn

3
@TheodoreNorvell- Tangent의 초기 버전이 그랬습니다. 필자는 저자이기 때문에 아마도 부정 행위 일 것입니다. 나는 다른 사람들을 위해 링크를 저장하지 않았으며, 그 연구를 한 지 오래되었습니다.
Telastyn

1
@Martijn 그러나을 사용할 수 있습니다 null. 예를 들어 포인터를 비교 null하여 부울 결과를 얻을 수 있습니다. 나는 두 가지 유형의 하단 유형이 있다는 답변이 제시되고 있다고 생각합니다. (a) 모든 유형의 하위 유형 인 유형이 결과를 제공하지 않는 계산을 나타내는 언어 (예 : 스칼라). 본질적으로 빈 유형이지만 기술적으로 종종 종료가 아닌 쓸데없는 값으로 채워집니다. (b) 탄젠트와 같은 언어. 하단 유형은 다른 모든 유형에서도 발견되는 유용한 값을 포함하기 때문에 다른 모든 유형의 하위 집합입니다-null.
Theodore Norvell

4
일부 언어에는 선언 할 수없는 유형 (널 리터럴에 공통)이있는 값이 있고 다른 언어에는 선언 할 수 있지만 값이없는 유형 (전통적인 맨 아래 유형)이 있으며 다소 비슷한 역할을 채우는 것이 흥미 롭습니다. .
Martijn

1

예, 이것은 매우 유용한 유형입니다. 그 역할은 대부분 유형 시스템의 내부에 있지만, 하단 유형이 공개적으로 나타나는 경우가 있습니다.

조건이 표현식 인 정적 유형의 언어를 고려하십시오 (따라서 if-then-else 구성 은 C 및 친구 의 삼항 연산자 로 두 배가되고 유사한 다 방향 사례 문이있을 수 있음). 함수형 프로그래밍 언어에는이 기능이 있지만 특정 명령 언어에서도 발생합니다 (ALGOL 60 이후). 그런 다음 모든 분기 표현식은 궁극적으로 전체 조건식의 유형을 생성해야합니다. 유형이 동일해야 할 수도 있습니다 (C의 삼항 연산자의 경우라고 생각합니다). 그러나 조건부도 조건문으로 사용할 수있을 때 특히 제한적입니다 (유용한 값을 반환하지 않음). 일반적으로 각 분기식이 (암시 적으로) 변환 가능 하기를 원합니다. 전체 표현식의 유형이 될 공통 유형으로 (공식 유형을 참조자가 C ++을 효과적으로 찾을 수 있도록 다소 복잡한 제한이있을 수 있지만, 여기에서는 자세히 설명하지 않습니다).

일반적인 종류의 변환이 이러한 조건식의 필요한 유연성을 허용하는 상황에는 두 가지가 있습니다. 하나는 이미 언급되었으며 결과 유형은 단위 유형입니다.void; 이것은 자연스럽게 다른 모든 유형의 수퍼 유형이며, 모든 유형을 (사소하게) 변환 할 수있게하여 조건식을 조건문으로 사용할 수 있습니다. 다른 하나는 표현식이 유용한 값을 반환하지만 하나 이상의 분기가 하나를 생성 할 수없는 경우를 포함합니다. 그들은 일반적으로 예외를 제기하거나 점프를 포함하며, (도달 할 수없는 지점에서) 전체 표현 유형의 값을 생산하도록 요구하는 것은 의미가 없습니다. 예외 발생 조항, 점프 및 호출에 영향을 미칠 수있는 예외 상황 절, 점프 및 호출을 제공하여 이러한 유형의 상황을 적절하게 처리 할 수 ​​있습니다.

*임의 유형으로의 변환 가능성을 제안 하는 최하위 유형을 작성 하는 것이 좋습니다. 예를 들어 선언하지 않는 재귀 함수에 대한 결과 유형을 추론하려고 할 때 내부적으로 다른 유용한 목적을 제공 할 수 *있습니다. 실제 유형은 비 재귀 분기에 의해 결정되며 재귀 분기는 비 재귀 분기의 공통 유형으로 변환됩니다. 비 재귀 분기 가 전혀 없으면 유형은 그대로 유지 *되며 함수가 재귀에서 돌아올 수있는 방법이 없음을 올바르게 나타냅니다. 이것 외에는 예외 던지기 함수의 결과 유형으로 사용할 수 있습니다.*길이가 0 인 시퀀스의 구성 요소 유형 (예 : 비어있는 목록)으로; 다시 한 번 유형의 표현식 [*](필수적으로 비어있는 목록) 에서 요소를 선택 하면 결과 유형 *은 이것이 오류없이 리턴 될 수 없음을 올바르게 표시합니다.


표현이 다른 것을 산출 할 수 없기 때문에 as var foo = someCondition() ? functionReturningBar() : functionThatAlwaysThrows()의 유형을 유추 할 수 있는 아이디어가 있습니까? fooBar
supercat

1
방금 답변의 첫 부분에서 단위 유형을 설명했습니다. 단위 형식을 반환하는 함수는 반환로 선언 된 것과 동일 voidC. 당신이 어떤 요소 -에 반환하지 함수의 유형 또는 목록에 대해 이야기 답변의 두 번째 부분 입니다 사실을 바닥 타입! (이것은 종종 _|_보다는 쓰지 않습니다 *. 왜 그런지 모르겠습니다. 아마도 (인간적인) 바닥처럼 보이기 때문일 것 입니다.)
andrewf

2
의심의 여지를 피하기 위해 : '유용한 것을 반환하지 않음'은 '반환하지 않음'과 다릅니다. 첫 번째는 단위 유형으로 표시됩니다. 하단 유형별 두 번째.
andrewf

@ andrewf : 예, 차이점을 이해합니다. 내 대답은 약간 길지 만, 내가 만들고 싶었던 요점은 단위 유형과 하단 유형이 특정 표현을보다 유연하게 (그러나 여전히 안전하게) 사용할 수있게하는 (다른 (그러나) 그러나) 비슷한 역할을한다는 것입니다.
Marc van Leeuwen

@ supercat : 그렇습니다. 경우는 유효 할 것이지만 현재 C에서 즉, 불법 ++ functionThatAlwaysThrows()명시 적으로 대체되었다 throw인해 표준에 특별한 언어. 이를 수행하는 유형이 있으면 개선 될 것입니다.
Marc van Leeuwen

0

일부 언어에서는 컴파일러와 개발자 모두에게이 함수에 대한 호출이 리턴되지 않을 것이라고 알리는 함수에 주석을 달 수 있습니다 (그리고 함수가 리턴 할 수있는 방식으로 작성된 경우 컴파일러는이를 허용하지 않습니다) ). 유용한 정보이지만, 결국 이와 같은 함수를 호출 할 수 있습니다. 컴파일러는 최적화, 데드 코드 등에 대한 경고를 제공하기 위해 정보를 사용할 수 있습니다. 따라서이 유형을 사용해야하는 매우 강력한 이유는 없지만 피해야 할 강력한 이유도 없습니다.

많은 언어에서 함수는 "void"를 반환 할 수 있습니다. 정확히 의미하는 것은 언어에 따라 다릅니다. C에서는 함수가 아무것도 반환하지 않음을 의미합니다. Swift에서는 함수가 가능한 값이 하나만있는 객체를 반환한다는 것을 의미하며, 가능한 값이 하나뿐이므로 value에 0 비트가 걸리고 실제로 코드가 필요하지 않습니다. 두 경우 모두 "하단"과 동일하지 않습니다.

"bottom"은 가능한 값이없는 유형입니다. 절대 존재할 수 없습니다. 함수가 "bottom"을 반환하면 반환 할 수있는 "bottom"유형의 값이 없으므로 실제로 반환 할 수 없습니다.

언어 디자이너가 그런 느낌이 든다면 해당 유형을 갖지 않아도됩니다. 구현은 어렵지 않습니다 (void를 리턴하고 "반환하지 않음"으로 표시된 함수와 동일하게 구현할 수 있습니다). 하단을 반환하는 함수에 대한 포인터와 같은 유형이 아니므로 void를 반환하는 함수에 대한 포인터를 혼합 할 수 없습니다.

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