함수는 Dance() 또는 Sleep() 등 액션을 수행하기 위한 인스트럭션을 제공하고, 제공한 입력에 따라 다양한 출력을 생성하는 재사용 가능 코드입니다.
함수는 행동에 대한 추상화를 제공합니다. 즉, 이 재사용 가능 함수는 코드의 다른 부분과 연관성이 없고 볼 필요가 없는 구현 디테일을 숨깁니다.
함수와 추상화의 예시로 메뉴에서 음식을 주문하는 상황을 들겠습니다. 음식 주문 함수는 다음과 같습니다.
OrderFood(MenuItem : string) : food = {...}음식점에서 음식을 주문할 때는 종업원에게 메뉴판의 어떤 음식을 원하는지 말합니다. 이것이 OrderFood("Ramen")입니다. 음식점에서 음식을 어떻게 준비하는지는 모르지만, 주문을 하고 나면 food, 즉 음식을 받을 것이라고 예상합니다. 다른 고객도 메뉴에서 각자 다른 음식을 주문하고, 음식을 받기를 기대합니다.
이것이 바로 함수가 유용한 이유입니다. 이런 인스트럭션을 한곳에서만 정의하면 됩니다. 이 경우는 누군가 음식을 주문했을 때 어떤 일이 발생하는지 정의하는 것입니다. 그런 다음 이 함수를 다른 컨텍스트에서 재사용할 수 있습니다. 예를 들면 음식점에서 메뉴를 보고 음식을 주문하는 모든 고객에게 재사용 가능합니다.
아래 섹션은 함수 생성 방법 및 정의된 함수 사용 방법을 설명합니다.
함수 정의하기
함수 시그니처(Function Signature)는 함수 이름(식별자)과 함수의 입력(파라미터) 및 출력(결과)을 선언합니다.
또한 Verse 함수는 함수 사용 또는 구현 방식을 지정하는 지정자도 가질 수 있습니다.
함수 바디는 함수가 호출됐을 때 무엇을 하는지 정의하는 코드 블록입니다.
아래 섹션에서는 이 개념을 더 자세히 설명합니다.
함수 구문은 다음과 같습니다.
Identifier(parameter1 : type, parameter2 : type) <specifier> : type = {}파라미터(Parameters)
파라미터는 함수 시그니처에서 선언되고 함수 바디에서 사용되는 입력 변수입니다. 함수를 호출할 때 파라미터가 있는 경우 파라미터에 값을 할당해야 합니다. 할당된 값은 함수의 실행인자라고 합니다.
함수는 Sleep() 함수처럼 파라미터를 갖지 않을 수도 있고, 파라미터를 필요한 만큼 가질 수도 있습니다. 함수 시그니처에서 파라미터를 선언하려면 괄호 () 사이에 식별자와 타입을 지정합니다. 다수의 파라미터가 있다면 쉼표 ,로 구분되어야 합니다.
예시:
Example(Parameter1 : int, Parameter2 : string) : string = {}다음은 모두 유효합니다.
Foo():void = {}
Bar(X:int):int = X
Baz(X:int, ?Y:int, ?Z:int = 0) = X + Y + Z구문 ?Y:int는 이름이 Y이고 타입이 int인 명명된 실행인자를 정의합니다.
구문 ?Z:int = 0은 이름이 Z이고 타입이 int인 명명된 실행인자를 정의합니다. 함수 호출 시 값을 반드시 제공할 필요는 없지만 값이 제공되지 않으면 0을 값으로 사용합니다.
결과
결과(Result)는 함수 호출 시 함수의 출력입니다. 반환 타입은 함수가 성공적으로 실행될 경우 어떤 값 타입을 기대할 수 있는지 지정합니다.
함수가 결과를 갖기를 원하지 않는다면 반환 타입을 void로 설정합니다. 반환 타입이 void인 함수는 함수 바디에서 결과 표현식을 지정하더라도 항상 false 값을 반환합니다.
지정자
정의된 함수의 행동을 설명하는 함수의 지정자(Specifier) 외에 함수의 식별자(이름)에도 지정자가 있을 수 있습니다. 예시:
Foo<public>(X:int)<decides>:int = X > 0이 예시는 Foo로 명명된 함수를 정의합니다. 이 함수는 퍼블릭 액세스가 가능하며 decides 이펙트를 갖습니다. 파라미터 목록 뒤, 반환 타입 앞의 지정자는 함수의 시맨틱을 설명하며 결과 함수의 타입에 기여합니다. 함수 이름의 지정자는 비저빌리티 등 정의된 함수의 이름과 관련된 행동만 나타냅니다.
함수 바디
함수 바디(Function Body)는 함수가 무엇을 하는지 정의하는 코드 블록입니다. 함수는 함수 바디의 함수 시그니처에 정의한 파라미터를 사용하여 결과를 생성합니다.
함수는 마지막으로 실행된 표현식에 의해 생성된 값을 자동으로 반환합니다.
예시:
Foo()<decides>:void = {}
Bar():int =
if (Foo[]):
1
else:
2함수 Bar()는 Foo[]의 실패 여부에 따라 1 또는 2를 반환합니다.
특정 값의 반환을 강제하려면(그리고 함수를 즉시 종료하려면) return 표현식을 사용합니다.
예시:
Minimum(X:int, Y:int):int =
if (X < Y):
return X
return Y표현식 return X는 Minimum 함수를 종료하고 X에 포함된 값을 함수의 호출자에게 반환합니다. 명시적 return 표현식이 여기에서 사용되지 않는 경우, 함수는 기본적으로 마지막으로 실행된 표현식을 반환합니다. 이로 인해 Y 값이 항상 반환되어, 올바르지 않은 결과로 이어질 가능성이 있습니다.
이펙트
함수의 이펙트는 함수가 호출되었을 때 추가로 수행될 수 있는 행동을 설명합니다. 구체적으로, 함수의 decides 이펙트는 호출자가 처리해야 하거나 decides로 표시하여 호출자에게 전파해야 하는 방식으로 함수가 실패할 수 있음을 나타냅니다.
예시:
Fail()<decides>:void = false?이는 항상 실패하는 함수를 정의합니다. 호출자는 실패를 처리 또는 전파해야 합니다. 이펙트가 함수의 지정자로 기술된 구문에 주목하세요. 이러한 이펙트가 있는 함수 타입은 타입 매크로를 통해 함수의 정의와 아주 유사하게 만들어질 수 있습니다.
type{_()<decides>void}decides 이펙트가 있는 함수도 함수가 성공할 경우 값을 반환할 수 있습니다.
예시:
First(Array:[]t, F(:t)<transacts><decides>:void where t:type)<transacts><decides>:t =
var ReturnOption:?t = false
for (Element : Array, F[Element], not ReturnOption?):
set ReturnOption = option{Element}
ReturnOption?이 함수는 제공된 decides 함수의 성공적인 실행으로 이어지는 첫 번째 배열 값을 결정합니다. 이 함수는 option 타입을 사용하여 반환 값을 보유하고 함수의 성공 또는 실패 여부를 결정합니다. 실패 컨텍스트 내에서는 명시적인 return 명령문이 허용되지 않기 때문입니다.
실패는 for 표현식과도 결합할 수 있습니다. for 표현식 내부에 실패 표현식이 있는 decides 함수는 for 표현식의 모든 반복작업이 성공하는 경우에만 성공합니다.
예시:
All(Array:[]t, F(:t)<transacts><decides>:void where t:type)<transacts><decides>:void =
for (Element : Array):
F[Element]이 함수는 Array의 모든 엘리먼트가 함수 F의 성공으로 이어지는 경우에만 성공합니다. Array의 어떤 입력이든 함수 F의 실패로 이어지는 경우, 함수 All은 실패합니다.
실패를 for 표현식과 결합하여 어떤 입력이 성공이나 실패로 이어지는지에 따라 입력을 필터링할 수도 있습니다.
예시:
Filter(Array:[]t, F(:t)<transacts><decides>:void where t:type)<transacts>:[]t =
for (Element : Array, F[Element]):
Element이 함수는 함수 F의 성공으로 이어지는 Array의 엘리먼트만 포함된 배열을 반환합니다.
함수 호출하기
함수 호출은 함수를 평가(호출 또는 인보크)하는 표현식입니다.
Verse에는 두 가지 양식의 함수 호출이 있습니다.
FunctionName(Arguments): 이 양식은 함수 호출이 성공하고 모든 컨텍스트에서 사용 가능해질 것을 요구합니다.FunctionName[Arguments]: 이 양식은 함수 호출이 실패할 수 있음을 나타냅니다. 이 양식을 사용하려면 함수는<decides>지정자와 함께 정의되고 실패 컨텍스트에서 호출되어야 합니다.
함수에 decides 이펙트가 없는 경우 인보크는 괄호를 통해 수행됩니다. 예시:
Foo()
Bar(1)
Baz(1, ?Y := 2)
Baz(3, ?Y := 4, ?Z := 5)
Baz(6, ?Z := 7, ?Y := 8)명명된 실행인자, 예를 들어 ?Y:int는 ?가 붙은 이름을 참조하고 := 오른쪽에 값을 제공하여 전달됩니다. 또한 명명된 실행인자 ?Z는 선택 사항입니다. 특히 호출 사이트의 명명된 실행인자 순서는 명명된 실행인자를 위한 값 생성 중 발생할 수 있는 부작용을 제외하면 무관합니다.
decides 이펙트가 있는 함수를 인보크하려면 대괄호를 사용해야 합니다. 그렇게 하면 decides 이펙트를 일으키는 배열 인덱싱을 허용하여 decides 이펙트가 표시된 함수와 유사한 구문을 따릅니다. 예시:
Foo()<decides>:void = {}
Bar():int =
if (Foo[]):
1
else:
2튜플 언패킹
다수의 실행인자를 수락하는 함수는 다수의 실행인자와 동일한 타입의 엘리먼트가 있는 단일 튜플 실행인자를 수락하는 함수로부터 인보크되었을 때 구분할 수 없습니다. 인보크되었을 때 구분이 어려운 현상은 각 함수의 타입에도 적용됩니다. 동일한 타입을 갖기 때문입니다.
예시:
Second(:any, X:t where t:type):t = X이는 다음과 같습니다.
Second(X:tuple(any, t) where t:type):t = X(1)둘 다 다음과 같이 인보크 가능합니다.
X := 1
Y := 2
Second(X, Y)또는
X:tuple(int, int) = (1, 2)
Second(X)둘 다 타입 type{_(:any, :t where t:type):t}를 충족합니다.
함수 타입
함수 타입은 언패킹된 튜플로 잠재적으로 정의되는 파라미터 타입, 이펙트, 결과 타입으로 구성됩니다. 예시:
type{_(:type1, :type2)<effect1>:type3}이 함수 타입은 type1 및 type2의 두 실행인자 또는 타입 tuple(type1, type2)의 실행인자 하나를 취해 이펙트 effect1을 생성하고 type3 타입 값을 반환합니다.
오버로드
다수의 함수는 같은 이름을 공유할 수 있습니다. 이때 두 개 이상의 함수를 충족하는 실행인자가 없어야 합니다. 이를 오버로드(Overload)라고 합니다.
예시:
Next(X:int):int = X + 1
Next(X:float):float = X + 1
int_list := class:
Head:int
Tail:?int_list = false
Next(X:int_list)<decides>:int_list = X.Tail?이 함수가 수락하는 실행인자에는 오버랩이 없습니다. 인보크할 올바른 함수는 제공된 타입에 의해 확실하게 해결될 수 있습니다. 예시:
Next(0)
Next(0.0)
Next(int_list{Head := 0, Tail := int_list{Head := 1}})그러나 다음은 허용되지 않습니다.
First(X:int, :any):int = X
First(X:[]int)<decides>int = X[0]튜플과 배열은 서브타입 관계를 가지기 때문입니다. 배열의 베이스 타입이 튜플의 모든 엘리먼트 타입의 수퍼타입인 경우 배열은 튜플의 수퍼타입입니다. 예시:
X := (1, 2)
First(X)이 예시에서 First 호출은 First의 다른 정의에 의해 충족될 수 있습니다. 클래스 및 인터페이스의 경우 오버로드가 일어날 수 없습니다. 클래스는 나중에 수정되어 인터페이스를 구현할 수 있고, 아니면 두 클래스가 변경되어 상속 관계를 가질 수도 있기 때문입니다. 대신 메서드 오버라이드를 사용해야 합니다. 예를 들면 다음과 같습니다.
as_int := interface:
AsInt():int
ToInt(X:as_int):int = X.AsInt()
thing1 := class(as_int):
AsInt():int = 1
thing2 := class(as_int):
AsInt():int = 2