DEV Community

CloudHolic
CloudHolic

Posted on

F# Tutorial (3) - Tuple

이번 글에선 F#에서의 Tuple에 대해 알아보겠습니다.

Definition

Tuple은 1개 이상 type으로 이루어진 이름없는 값들의 그룹입니다.

(1, 2)
("one", "two", "three")
(a, b)
("one", 1, 2.0f)
(a + 1, b + 1)
Enter fullscreen mode Exit fullscreen mode

위의 예시들에서 볼 수 있듯이 각 원소들의 type이 달라도 상관없습니다.
또한 FP에서 '값'이란 것은 결국 함수이기 때문에 안에 함수가 들어가도 상관없고요.

또한 값을 여러 개 입력받을때도 유용하지만, 여러 개 출력할 때도 유용합니다.

let divRem a b =
    let x = a / b
    let y = a % b
    (x, y)      // x와 y를 동시에 리턴
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

개별 원소에 접근하기

이러한 tuple의 각 원소에 접근하는 방법엔 여러 가지가 있습니다.

먼저 let Binding을 사용할 수 있겠군요.

let (a, b) = (1, 2) // a = 1, b = 2   
Enter fullscreen mode Exit fullscreen mode

다음으론 내장 함수인 fst, snd를 사용하는 방법도 있습니다. fst함수는 해당 tuple의 첫 번째 원소를, snd함수는 두 번째 원소를 리턴해줍니다.

let c = fst (1, 2, 3)   // c = 1
let d = snd (1, 2, 3)   // d = 2
Enter fullscreen mode Exit fullscreen mode

그런데 세번째 이후의 원소를 리턴하는 함수는 없습니다. 만일 필요하다면 아래와 같이 직접 만들 수는 있죠.

let third (_, _, c) = c
Enter fullscreen mode Exit fullscreen mode

나중에 알아볼 Pattern Matching을 이용해도 됩니다.

let print tuple1 = 
    match tuple1 with
        | (a, b) -> printfn "Pair %A %A" a b
Enter fullscreen mode Exit fullscreen mode

함수의 인자로 쓰는 거라면 아래처럼 명시적으로 각 원소로 분해해서 받아도 되고요.

let distance ((x1, y1): float * float) ((x2, y2): float * float)) =
    (x1 * x2) - (y1 * y2)
Enter fullscreen mode Exit fullscreen mode

Tuple 내에서 관심이 없는 원소가 있다면, _를 사용해 받으면 해당 원소에 대한 새 할당을 피할 수 있습니다.

let (a, _) = (1, 2) // 2는 버려집니다.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

하지만 내부 구현은 전혀 다르기 때문에 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
Enter fullscreen mode Exit fullscreen mode

Reference tuple과 struct tuple 간에 컨버팅을 하고 싶다면, 다음과 같이 명시적으로 각각의 원소들을 새로 집어넣어서 새 tuple을 만들어야 합니다.

let (a, b) = (1, 2)
let struct (c, d) = struct (a, b)
Enter fullscreen mode Exit fullscreen mode

그러면 언제 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);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
open Interop

let struct (x, y) = Sample.AddOne(struct (1, 2))    // x = 2, y = 3
Enter fullscreen mode Exit fullscreen mode

위와 같이 C# Tuple과 F# struct tuple은 근본적으로 동일하기 때문에 별다른 과정 없이 자연스럽게 interop가 가능합니다.

다음 글에선 Collection을 다루도록 하겠습니다.

Top comments (0)