파라미터 타입은 파라미터를 취할 수 있는 모든 타입을 가리킵니다. Verse에서는 파라미터 타입을 사용하여 일반화된 데이터 구조와 연산을 정의할 수 있습니다. 파라미터 타입을 실행인자로 사용하는 방법은 두 가지가 있습니다. 함수를 명시적 또는 묵시적 타입의 실행인자로 사용하거나, 클래스를 명시적 타입의 실행인자로 사용하는 것입니다.
이벤트는 파라미터 타입의 일반적인 예시로, UEFN의 모든 장치에서 광범위하게 사용됩니다. 예를 들어 버튼 장치에는 플레이어가 버튼과 상호작용할 때마다 트리거되는 InteractedWithEvent가 있습니다. 실제 작동하는 파라미터 타입을 보려면 커스텀 카운트다운 타이머 튜토리얼의 CountdownEndedEvent를 확인하세요.
명시적 타입 실행인자
box 하나가 실행인자 두 개를 취한다고 가정해 보겠습니다. 여기에서 first_item은 ItemOne을 초기화하고, second_item은 ItemTwo를 초기화하며 두 타입 모두 type입니다. 이 경우, first_item 및 second_item은 모두 클래스에 대해 명시적 실행인자인 파라미터 타입의 예시가 됩니다.
box(first_item:type, second_item:type) := class:
ItemOne:first_item
ItemTwo:second_itemtype은 first_item 및 second_item의 타입 실행인자이기 때문에 box 클래스는 둘 중 어느 타입으로든 생성될 수 있습니다. 두 string 값의 박스, 두 int 값의 박스, string 하나와 int 하나의 박스 또는 두 박스의 박스를 만들 수 있습니다.
다른 예시로는 어떤 타입이든 취하여 해당 타입의 option을 반환하는 MakeOption() 함수를 가정해 볼 수 있습니다.
MakeOption(t:type):?t = false
IntOption := MakeOption(int)
FloatOption := MakeOption(float)
StringOption := MakeOption(string)또한 MakeOption() 함수가 array 또는 map 같은 다른 컨테이너 타입을 반환하도록 수정할 수도 있습니다.
묵시적 타입 실행인자
함수에서 묵시적 타입 실행인자를 사용하려면 where 키워드를 사용합니다. 예를 들어 ReturnItem() 함수의 경우 단순히 파라미터를 취해 다음을 반환합니다.
ReturnItem(Item:t where t:type):t = Item여기에서 t는 ReturnItem() 함수의 묵시적 타입 파라미터로, type 타입 실행인자를 취해 즉시 반환합니다. t 타입은 이 함수로 전달할 수 있는 Item 타입을 제한합니다. 이 경우, t는 type 타입이기 때문에 어떤 타입으로든 ReturnItem()을 호출할 수 있습니다. 함수에서 묵시적 파라미터 타입을 사용하는 이유는 전달되는 타입에 상관없이 작동하는 코드를 작성할 수 있기 때문입니다.
예를 들어 다음과 같이 작성하는 대신에
ReturnInt(Item:int):int = Item
ReturnFloat(Item:float):float = Item아래와 같이 단일 함수를 작성할 수 있습니다.
ReturnItem(Item:t where t:type):t = Item이 코드에서 ReturnItem()은 t가 어떤 특정 타입인지 알 필요가 없습니다. 즉, t의 타입과는 무관하게 작동합니다.
ReturnItem()이 어떻게 사용되는지에 따라 t에 사용되는 실제 타입이 결정됩니다. 예를 들어 ReturnItem()에 실행인자 0.0을 전달하여 호출하면 t는 float가 됩니다.
ReturnItem("t") # t is a string
ReturnItem(0.0) # t is a float여기에서 "hello"와 0.0은 ReturnItem()으로 전달되는 명시적 실행인자(Item)입니다. Item의 묵시적 타입이 t, 즉 어떤 type이든 될 수 있기 때문에 둘 모두 작동합니다.
파라미터 타입을 함수에 대한 묵시적 실행인자로 사용하는 또 다른 예시로는 box 클래스에서 작동하는 MakeBox() 함수를 다음과 같이 가정해 볼 수 있습니다.
box(first_item:type, second_item:type) := class:
ItemOne:first_item
ItemTwo:second_item
MakeBox(ItemOneVal:ValOne, SecondItemVal:ValTwo where ValOne:type, ValTwo:type):box(ValOne, ValTwo) =
box(ValOne, ValTwo){ItemOne := ItemOneVal, ItemTwo := SecondItemVal}
Main():void =
MakeBox("A", "B")
MakeBox(1, "B")
여기에서 MakeBox() 함수는 모두 type인 FirstItemVal 및 SecondItemVal이라는 두 개의 실행인자를 취하여 (type, type) 타입의 박스를 반환합니다. 여기에서 type을 사용한다는 것은 MakeBox에 반환되는 박스가 배열, 스트링, 함수 등 아무 오브젝트 두 개로 구성될 수 있다는 것을 알려주는 것입니다. MakeBox() 함수는 두 실행인자를 모두 Box에 전달하고, 이를 사용하여 박스를 생성하여 반환합니다. 여기에서 box와 MakeBox() 모두 함수 호출과 동일한 구문을 사용하고 있다는 점에 유의해야 합니다.
이것의 내장 샘플은 아래와 같은 Map 컨테이너 타입 함수입니다.
Map(F(:t) : u, X : []t) : []u =
for (Y : X):
F(Y)타입 컨스트레인트
표현식 타입에 컨스트레인트를 지정할 수 있습니다. 현재 유일하게 지원되는 컨스트레인트는 서브타입이며, 묵시적 타입의 파라미터에만 해당됩니다. 예시:
int_box := class:
Item:int
MakeSubclassOfIntBox(NewBox:subtype_box where subtype_box:(subtype(int_box))) : tuple(subtype_box, int) = (NewBox, NewBox.Item)이 예시에서 MakeSubclassOfIntBox()는 IntBox에서 서브클래스를 지정하는 클래스를 전달한 경우에만 컴파일되는데, SubtypeBox의 타입이 (subtype(IntBox))이기 때문입니다. type은 subtype(any)를 줄인 것으로 보면 됩니다. 즉, 이 함수는 모든 타입인 any의 모든 서브타입을 허용합니다.
공변성 및 반공변성
공변성과 반공변성은 컴포짓 타입 또는 함수에서 타입이 사용될 때 두 타입 간의 관계를 나타냅니다. 두 타입은 어떻게든 서로 관련이 되어 있는데, 예를 들어 한 서브클래스와 다른 서브클래스는 코드의 특정 부분에서 쓰이는 방식에 따라 공변성 또는 반공변성이 됩니다.
공변성(Covariant): 보다 일반적인 것이 예상되는 코드에서 더 구체적인 타입을 사용합니다.
반공변성(Contravariant): 보다 구체적인 것이 예상되는 코드에서 더 일반적인 타입을 사용합니다.
예를 들어 어떤 comparable(예: float)이든 허용되는 상황에서 int를 사용할 수 있다면 보다 일반적인 타입이 예상되는 상황에서 int는 공변적으로 작동하게 됩니다. 반대로, 보통 int가 사용되는 상황에서 어떤 comparable이든 사용할 수 있다면 비교적 구체적인 것이 예상되는 상황에서 더 일반적인 타입을 사용하므로 comparable은 반공변적으로 작동하게 됩니다.
파라미터 타입의 공변성 및 반공변성의 예시는 다음과 같습니다.
MyFunction(Input:t where t:type):logic = true여기에서 t는 함수의 입력으로서 반공변적으로 사용되고, logic은 함수의 출력으로서 공변적으로 사용됩니다.
여기에서 명심할 것은 두 타입의 본질부터가 서로 공변적, 반공변적인 것이 아니라 코드에서 사용되는 방식에 따라서 공변적, 반공변적으로 작용한다는 것입니다.
공변적
공변성은 일반적인 것을 예상하는 경우에 보다 구체적인 것을 사용한다는 의미입니다. 보통은 함수의 출력용으로 사용됩니다. 함수에 대한 입력이 아닌 모든 타입의 사용 사례는 공변적 사용 사례입니다.
아래의 일반적인 파라미터 타입 예시에는 payload가 공변적으로 작동합니다.
DoSomething():int =
payload:int = 0예를 들어 animal 클래스가 있고, animal의 서브클래스로 지정하는 cat 클래스가 있다고 가정할 수 있습니다. 또한 AdoptPet() 함수로 애완동물을 입양하는 pet_sanctuary 클래스도 있습니다. 여기에서 어떤 애완동물을 얻을지 모르므로 AdoptPet()에서는 일반적인 animal을 반환합니다.
animal := class:
cat := class(animal):
pet_sanctuary := class:
AdoptPet():animal = animal{}여기에서 고양이만 취급하는 동물 보호소가 따로 있다고 가정할 수 있습니다. 이 클래스 cat_sanctuary는 pet_sanctuary의 서브클래스입니다. 여기는 고양이 보호소이므로 AdoptPet()을 오버라이드하여 animal 대신 cat만 반환하게 합니다.
cat_sanctuary := class(pet_sanctuary):
AdoptPet<override>():cat = cat{}이 경우, AdoptPet()의 cat 반환 타입은 animal에 대해 공변적입니다. 원본에서는 보다 일반적인 타입을 사용했지만 여기에서는 보다 구체적인 타입을 사용하고 있습니다.
이를 컴포짓 타입에도 적용할 수 있습니다. cat 배열이 주어졌으므로 cat 배열을 사용하여 animal 배열을 초기화할 수 있습니다. 하지만 animal은 해당 서브클래스인 cat으로 변환될 수 없기 때문에 반대로는 작동하지 않습니다. cat 배열은 animal 배열에 대해 공변적인데, 이는 더 좁은 타입을 더 일반적인 타입으로 취급하고 있기 때문입니다.
CatArray:[]cat = array{}
AnimalArray:[]animal = CatArray함수 입력은 공변적으로 사용될 수 없습니다. 다음 코드는 실패할 수밖에 없는데, AnimalExample()을 CatExample()에 할당하는 타입인 cat이 AnimalExample()의 반환 타입이 되기에는 너무 구체적이기 때문입니다. 순서를 뒤집어서 CatExample()을 AnimalExample에 할당하는 방식으로 하면 cat이 animal에서 하위 타입이 되기 때문에 정상적으로 작동하게 됩니다.
CatExample:type{CatFunction(MyCat:cat):void} = …
AnimalExample:type{AnimalFunction(MyAnimal:animal):void} = CatExample다음은 변수 t가 공변적으로만 사용되는 추가 예시입니다.
# The line below will fail because t is used only covariantly.
MyFunction(:logic where t:type):?t = false반공변적
반공변성은 공변성과 정반대로, 구체적인 것을 예상하는 경우에 보다 일반적인 것을 사용한다는 의미입니다. 보통은 함수의 입력으로 사용됩니다. 아래의 일반적인 파라미터 타입 예시에는 payload가 반공변적으로 작동합니다.
DoSomething(Payload:payload where payload:type):void반려동물 보호소에 새 고양이를 맞이하는 구체적인 절차가 있다고 가정할 수 있습니다. 따라서 pet_sanctuary에 RegisterCat()이라는 새 메서드를 추가했습니다.
pet_sanctuary := class:
AdoptPet():animal = animal{}
RegisterCat(NewAnimal:cat):void = {}cat_sanctuary에서는 모든 cat이 animal이라는 것을 이미 알고 있으므로 animal을 파라미터 타입으로 받도록 메서드를 오버라이드합니다.
cat_sanctuary := class(pet_sanctuary):
AdoptPet<override>():cat = cat{}
RegisterCat<override>(NewAnimal:animal):void = {}여기에서 animal은 cat에 대해 반공변적인데 이는 보다 구체적인 것이 어울리는 경우에 보다 일반적인 것을 사용하고 있기 때문입니다.
where 키워드로 시작된 묵시적 타입을 사용하면 공변적으로 오류가 발생합니다. 예를 들어 여기에서 사용된 payload는 공변적으로 사용되었지만 실행인자로 정의되어 있지 않기 때문에 오류가 발생합니다.
DoSomething(:logic where payload:type) : ?payload = false이것을 수정하려면 타입 파라미터를 제외하고 다음과 같이 수정할 수 있습니다.
DoSomething(:logic) : ?false = false반공변적으로만 사용되면 오류가 발생하지 않지만 false를 사용하는 대신 any로 수정할 수 있습니다. 예시:
ReturnFirst(First:first_item, :second_item where first_item:type, second_item:type) : first_item = Firstsecond_item이 type 타입이었고 반환되지 않았으므로 두 번째 예시에서 any로 변경한 후 타입 체크를 방지할 수 있습니다.
ReturnFirst(First:first_item, :any where first_item:type) : first_item = First타입 first_item을 any 또는 false로 대체하면 정확성이 떨어집니다. 예를 들어 다음 코드는 컴파일에 실패합니다.
ReturnFirst(First:any, :any) :any = First
Main() : void =
FirstInt:int = ReturnFirst(1, "ignored")알려진 제한 사항
데이터 타입에 대해 명시적인 타입 파라미터는 클래스와만 사용할 수 있고, 인터페이스 또는 구조체와는 사용할 수 없습니다. 파라미터 타입과 관련된 상속 역시 허용되지 않습니다. | Verse |
파라미터 타입은 재귀가 직접적인 한, 자기 자신을 재귀적으로 참조할 수 있습니다. 파라미터 타입은 다른 파라미터 타입을 재귀적으로 참조할 수 없습니다. | Verse |
현재 클래스는 변경 불가능한 파라미터 타입 데이터만 지원합니다. 예를 들어 이 코드는 | Verse |
묵시적 타입 파라미터가 함수와 자유롭게 결합할 수 있듯이 명시적 파라미터는 클래스와 자유롭게 결합할 수 있습니다. | Verse |