페이지 끝에 전체 함수 문법 치트 시트가 있습니다.
함수는 화살표와 반환 표현식으로 선언할 수 있습니다.
let greet = (name) => "Hello " ++ name
이 함수 선언은 greet
라는 이름으로 할당됩니다. 다음과 같이 호출할 수 있습니다.
greet("world!") /* "Hello world!" */
함수가 여러 인자를 받는 경우 콤마(,)를 구분자로 선언합니다.
let add = (x, y, z) => x + y + zadd(1, 2, 3) /* 6 */
긴 함수를 작성하려면 내용을 블록으로 감쌉니다.
let greetMore = (name) => {let part1 = "Hello"part1 ++ " " ++ name}
함수에 인자가 없다면 다음과 같이 작성하세요. let greetMore = () => {...}
이름이 있는 인자
여러 인자를 받는 함수, 특히 인자가 동일한 타입인 함수는 호출할 때 혼동 될 수 있습니다.
let addCoordinates = (x, y) => {/* 여기서 x, y를 사용 */}/* ... */addCoordinates(5, 6) /* 어떤게 x고 y지? */
이런 경우 인자 이름 앞에 ~
기호를 붙여 인자에 이름을 붙일 수 있습니다.
let addCoordinates = (~x, ~y) => {/* 여기서 x, y를 사용 */}/* ... */addCoordinates(~x=5, ~y=6)
그리고 이름이 있는 인자는 순서에 관계없이 전달할 수 있습니다.
addCoordinates(~y=6, ~x=5)
선언에서 ~x
부분은 함수가 x
로 표시된 인자를 받아들이고 동일한 이름으로 함수에서 참조 할 수 있음을 의미합니다.
간결하게 작성하기 위해 함수 내부에서 다른 이름으로 인자를 참조 할 수도 있습니다.
let drawCircle = (~radius as r, ~color as c) => {setColor(c)startAt(r, r)/* ... */}drawCircle(~radius=10, ~color="red")
눈치 채셨겠지만, 사실 (~radius)
는 (~radius as radius)
의 축약입니다.
인자에 타입을 기재하는 문법은 다음과 같습니다.
let drawCircle = (~radius as r: int, ~color as c: string) => {/* 여기에 코드 작성 */}
이름이 있는 선택 인자
이름이 있는 인자는 선언시 선택 사항으로 만들 수 있습니다. 그리고 함수를 호출 할 때 생략 할 수 있습니다.
/* radius 는 생략 가능 */let drawCircle = (~color, ~radius=?, ()) => {setColor(color)switch radius {| None => startAt(1, 1)| Some(r_) => startAt(r_, r_)}}
이 구문에서 radius
는 option
타입에 래핑되며 기본값은 None
입니다.
값이 전달되는 경우 Some
으로 래핑됩니다. 따라서 radius
의 타입 값은 None | Some(int)
입니다.
option
타입에 대해 더 알아보려면 여기를 참고하세요.
참고 타입 시스템을 위해 선택 인자가 있을때마다 그 뒤에 위치 인자(이름이 지정되지 않았고 선택 인자가 아닌 인자)가 하나 이상 있는지 확인해야합니다.
없는 경우 더미 unit
(일명 ()
) 인자를 기재합니다.
(역주: 위 코드 조각에서 ~radius=?, ()
부분에 해당하는 설명입니다.)
시그니쳐 및 타입 어노테이션
이름과 선택 인자가 있는 함수는 시그니쳐 및 타입 어노테이션과 관련해 혼란스러울 수 있습니다.
실제로 이름과 선택이 지정된 인자의 타입은 함수를 호출하는지, 함수 내부에서 작업하는지에 따라 다르게 보입니다.
함수 외부에서 원시 값을 전달하거나 (예:int
) 전달하지 않을 수 있습니다.
함수 내부에서는 인자가 항상 있지만 그 값은 option 입니다. (option <int>
)
즉, 함수 타입을 작성하는지, 인자 타입 어노테이션을 작성하는지에 따라 타입 시그니쳐가 다릅니다.
첫 번째는 원시 값이고 두 번째는 option 입니다.
이전 예제로 돌아가서 인자에 시그니쳐와 타입 어노테이션을 모두 추가하면 다음과 같은 결과를 얻을 수 있습니다.
let drawCircle: (~color: color, ~radius: int=?, unit) => unit =(~color: color, ~radius: option<int>=?, ()) => {setColor(color)switch radius {| None => startAt(1, 1)| Some(r_) => startAt(r_, r_)}}
첫 번째 줄은 함수의 시그니처이며, 인터페이스 파일에서 정의합니다. (Signatures 참조)
함수의 시그니처는 외부와 상호 작용하는 타입을 설명합니다. 실제로 호출 될 때 int
를 예상하기 때문에 radius
가 int
타입이 됩니다.
두 번째 줄에서는 함수 내부에서 인자를 사용할 때 인자 타입을 기억할 수 있도록 인자에 어노테이션을 추가합니다.
여기서 실제로 radius
는 함수 내부에서 option<int>
가 됩니다.
이름 있는 선택 인자를 사용해 함수 시그니처를 작성할 때 어려움을 겪는 경우 위 내용을 기억하세요!
명시적으로 전달되는 선택 인자
값이 None
인지 Some(a)
인지 모르는 상태에서 값을 함수에 전달하고 싶을 때가 있습니다. 이렇게 말이죠.
let result =switch payloadRadius {| None => drawCircle(~color, ())| Some(r) => drawCircle(~color, ~radius=r, ())}
코드가 불필요하게 길어지네요. 이렇게 쓸 수 있습니다.
let result = drawCircle(~color, ~radius=?payloadRadius, ())
설명하자면 "radius
가 선택 인자고
int
값을 전달해야 한다는 것을 알지만,
전달하려는 값이 None
인지 Some(val)
모른다.
그래서 option
그대로 전달하겠다."
기본값이 있는 선택 인자
이름이 있는 선택 인자도 기본값을 설정할 수 있습니다. 이 경우 option
타입으로 래핑되지 않습니다.
let drawCircle = (~radius=1, ~color, ()) => {setColor(color)startAt(radius, radius)}
재귀함수
리스크립트는 기본적으로 함수내에서 재귀적으로 호출하는 것을 방지하고 있습니다.
재귀함수를 만들려면 let
뒤에 rec
키워드를 추가합니다.
let rec neverTerminate = () => neverTerminate()
간단한 재귀함수는 다음과 같습니다.
/* 리스트 중 하나가 `item`과 같을때까지 모든 항목을 재귀적으로 확인합니다. *//* 일치하는 항목이 있으면 'true'를 반환하고, 그렇지 않으면 'false'를 반환합니다. */let rec listHas = (list, item) =>switch list {| list{} => false| list{a, ...rest} => a === item || listHas(rest, item)}
function listHas(_list, item) {while (true) {var list = _list;if (!list) {return false;}if (list.hd === item) {return true;}_list = list.tl;continue;}}
함수를 재귀적으로 호출하는 것은 성능과 호출 스택에 좋지 않습니다. 그러나 리스크립트는 지능적으로 꼬리 재귀을 사용해 빠른 자바스크립트 루프로 컴파일합니다. 위 코드의 JS 결과물을 확인해보세요!
상호 재귀 함수
상호 재귀 함수는 rec
키워드를 사용해 단일 재귀 함수처럼 시작된 다음 and
와 함께 연결합니다.
let rec callSecond = () => callFirst()and callFirst = () => callSecond()
function callSecond(_param) {while (true) {_param = undefined;continue;}}function callFirst(_param) {while (true) {_param = undefined;continue;}}
언커리드 함수
리스크립트의 함수는 기본적으로 커리드 함수며, 컴파일 된 JS 결과물에서 우리가 지불하는 몇 가지 성능 패널티 중 하나입니다. 컴파일러는 가능한 한 이러한 커링을 제거하기 위해 최선을 다합니다. 그러나 특정 상황에서는 언커링을 보장 할 수 있습니다. 이 경우 함수의 매개 변수 목록에 점을 넣으세요.
let add = (. x, y) => x + yadd(. 1, 2)
function add(x, y) {return (x + y) | 0;}add(1, 2);
단일 단위 ()
인자로 커리드 함수를 호출해야하는 경우 ignore()
함수를 사용할 수 있습니다.
let echo = (. a) => aecho(. ignore())
function echo(a) {return a;}echo(undefined);
언커리드 함수의 타입을 선언할 때에도 마찬가지로 괄호 앞에 .을 붙여야 합니다.
참고: 선언과 호출 모두 언커리 어노테이션이 있어야합니다. 이는 보증/요구 사항의 일부입니다.
이 기능은 사소해 보이지만 실제로는 함수형 언어에서 가장 중요한 기능 중 하나입니다.
JS 출력에서 커리
런타임에 대한 언급을 제거하려면 사용하는 것이 좋습니다.
팁
함수 문법의 치트 시트입니다.
선언
/* 익명함수 */(x, y) => 1/* 함수에 이름을 붙이다 */let add = (x, y) => 1/* 이름이 있는 인자와 약어 정의 */let add = (~first as x, ~second as y) => x + y/* 약어 정의 빼기 */let add = (~first, ~second) => first + second/* 기본값과 이름이 있는 인자와 약어 정의 */let add = (~first as x=1, ~second as y=2) => x + y/* 약어 정의 빼기 */let add = (~first=1, ~second=2) => first + second/* 선택 인자와 약어 정의 */let add = (~first as x=?, ~second as y=?) => switch x {...}/* 약어 정의 빼기 */let add = (~first=?, ~second=?) => switch first {...}
+ 타입 어노테이션
/* 익명 함수 */(x: int, y: int): int => 1/* 함수에 이름을 붙이다 */let add = (x: int, y: int): int => 1/* 이름이 있는 인자와 약어 정의 */let add = (~first as x: int, ~second as y: int) : int => x + y/* 약어 정의 빼기 */let add = (~first: int, ~second: int) : int => first + second/* 기본값과 이름이 있는 인자와 약어 정의 */let add = (~first as x: int=1, ~second as y: int=2) : int => x + y/* 약어 정의 빼기 */let add = (~first: int=1, ~second: int=2) : int => first + second/* 선택 인자와 약어 정의 */let add = (~first as x: option<int>=?, ~second as y: option<int>=?) : int => switch x {...}/* 약어 정의 빼기 *//* 참고로 호출하는 곳에서는 `option<int>`가 아닌 `int`로 호출 합니다. *//* 함수 내부에서 `first`, `second`는 `option<int>` 입니다. */let add = (~first: option<int>=?, ~second: option<int>=?) : int => switch first {...}
호출
add(x, y)/* 이름이 있는 인자에 명시적으로 전달 */add(~first=1, ~second=2)/* 동일한 이름을 가진 값을 암묵적으로 전달 */add(~first, ~second)/* 기본값이 있는 함수 호출, 일반 호출과 동일 */add(~first=1, ~second=2)/* 명시적으로 선택 인자 호출 */add(~first=?Some(1), ~second=?Some(2))/* 동일한 이름을 가진 값을 암묵적으로 전달 */add(~first?, ~second?)
+ 타입 어노테이션
/* 이름이 있는 인자에 명시적으로 전달 */add(~first=1: int, ~second=2: int)/* 동일한 이름을 가진 값을 암묵적으로 전달 */add(~first: int, ~second: int)/* 기본값이 있는 함수 호출, 일반 호출과 동일 */add(~first=1: int, ~second=2: int)/* 명시적으로 선택 인자 호출 */add(~first=?Some(1): option<int>, ~second=?Some(2): option<int>)/* 타입 어노테이션을 사용하는 경우 동일한 이름을 가진 값을 암묵적으로 전달 하는 방법은 없습니다. */
독립적인 함수 시그니쳐
/* 첫번째, 두번째 인자 타입, 반환 타입 */type add = (int, int) => int/* 이름이 있는 인자 */type add = (~first: int, ~second: int) => int/* 이름이 있는 인자 */type add = (~first: int=?, ~second: int=?, unit) => int
인터페이스 파일 내부에서
인터페이스 파일 (.resi
)의 구현 파일 (.res
)에서 함수 어노테이션을 달려면 이렇게 하세요.
let add: (int, int) => int
타입 어노테이션 부분은 이전 "+ 타입 어노테이션" 섹션과 동일합니다.
let add: myType
을 type add = myType
과 혼동하지 마세요.
.resi
인터페이스 파일에서 사용되는 경우 전자는 myType
타입으로 어노테이션을 달면서 add
를 내 보냅니다.
후자는 값이 myType
타입 인 add
타입을 내보냅니다.