F# Tutorial (3) - Tuple

CloudHolic·2021년 9월 8일

F# Tutorial

목록 보기
3/6

이번 글에선 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을 다루도록 하겠습니다.

0개의 댓글