이번 글에선 F#에서의 Tuple에 대해 알아보겠습니다.
Definition
Tuple은 1개 이상 type으로 이루어진 이름없는 값들의 그룹입니다.
(1, 2)
("one", "two", "three")
(a, b)
("one", 1, 2.0f)
(a + 1, b + 1)
위의 예시들에서 볼 수 있듯이 각 원소들의 type이 달라도 상관없습니다.
또한 FP에서 '값'이란 것은 결국 함수이기 때문에 안에 함수가 들어가도 상관없고요.
또한 값을 여러 개 입력받을때도 유용하지만, 여러 개 출력할 때도 유용합니다.
let divRem a b =
let x = a / b
let y = a % b
(x, y) // x와 y를 동시에 리턴
Type of tuple
Tuple의 type은 *
를 사용해서 표기합니다.
뒤에서 볼 struct tuple의 경우 기존 type을 struct( )
로 감싸주면 그게 곧 struct tuple의 type이 됩니다.
let a = (1, 2.0f, "three") // int * float * string
let b = struct (1, 2.0f, "three") // struct (int * float * string)
개별 원소에 접근하기
이러한 tuple의 각 원소에 접근하는 방법엔 여러 가지가 있습니다.
먼저 let
Binding을 사용할 수 있겠군요.
let (a, b) = (1, 2) // a = 1, b = 2
다음으론 내장 함수인 fst
, snd
를 사용하는 방법도 있습니다. fst
함수는 해당 tuple의 첫 번째 원소를, snd
함수는 두 번째 원소를 리턴해줍니다.
let c = fst (1, 2, 3) // c = 1
let d = snd (1, 2, 3) // d = 2
그런데 세번째 이후의 원소를 리턴하는 함수는 없습니다. 만일 필요하다면 아래와 같이 직접 만들 수는 있죠.
let third (_, _, c) = c
나중에 알아볼 Pattern Matching을 이용해도 됩니다.
let print tuple1 =
match tuple1 with
| (a, b) -> printfn "Pair %A %A" a b
함수의 인자로 쓰는 거라면 아래처럼 명시적으로 각 원소로 분해해서 받아도 되고요.
let distance ((x1, y1): float * float) ((x2, y2): float * float)) =
(x1 * x2) - (y1 * y2)
Tuple 내에서 관심이 없는 원소가 있다면, _
를 사용해 받으면 해당 원소에 대한 새 할당을 피할 수 있습니다.
let (a, _) = (1, 2) // 2는 버려집니다.
Struct tuple
지금까지 사용한 tuple은 모두 reference tuple입니다. 이와는 반대로 struct tuple도 존재합니다. Struct tuple은 Reference tuple과 달리 value type이며, heap이 아닌 stack에 저장되어서 약간의 성능 향상을 꾀할 수 있습니다.
Struct tuple은 다음과 같이 tuple 앞에 struct
가 붙는다는 것 외에 다른 모든 점이 동일합니다. 적어도 사용에 있어서는요.
let str_a = struct (1, 2, 3)
let struct (a, b) = struct (1, 2) // a = 1, b = 2
하지만 내부 구현은 전혀 다르기 때문에 reference tuple과 struct tuple 간의 암시적인 컨버팅은 불가능합니다.
let (a, b) = struct (1, 2) // Compile error!
let struct (c, d) = (1, 2) // Compile error!
// 이런 꼼수도 불가능합니다.
let convert (tpl: struct(int * int)): int * int = tpl
Reference tuple과 struct tuple 간에 컨버팅을 하고 싶다면, 다음과 같이 명시적으로 각각의 원소들을 새로 집어넣어서 새 tuple을 만들어야 합니다.
let (a, b) = (1, 2)
let struct (c, d) = struct (a, b)
그러면 언제 struct tuple을 써야 할까요? 저는 크게 다음과 같은 케이스에서 사용을 고려할 수 있다고 생각합니다.
- 사용하고자 하는 tuple의 원소 개수가 많거나 개별 원소가 복잡한 type을 가지고 있을 때, 성능 향상을 위해 사용
- C#과의 interop가 필요한 경우
이 중 후자의 경우에 대해 더 알아보겠습니다. C# 7.0 이후 버전에서 tuple은 System.ValueTuple로 컴파일되며 이는 value type입니다. 그리고 F#에서의 struct tuple 역시 System.ValueTuple로 컴파일됩니다.
즉, C#의 Tuple과 F#의 struct tuple은 완전히 동일한 타입이기 때문에 C#과의 interop를 고려하고 있다면 struct tuple을 사용해야만 합니다.
namespace Interop
{
public static class Sample
{
public static (int, int) AddOne((int x, int y) tpl)
{
return (tpl.x + 1, tpl.y + 1);
}
}
}
open Interop
let struct (x, y) = Sample.AddOne(struct (1, 2)) // x = 2, y = 3
위와 같이 C# Tuple과 F# struct tuple은 근본적으로 동일하기 때문에 별다른 과정 없이 자연스럽게 interop가 가능합니다.
다음 글에선 Collection을 다루도록 하겠습니다.
Top comments (0)