복사 및 업데이트 레코드 식

복사 및 업데이트 레코드 식 (Copy and Update Record Expressions)

오늘은 F#의 **복사 및 업데이트 레코드 식(Copy and Update Record Expression)**에 대해 알아볼게요. 이 식은 기존 레코드를 그대로 복사하면서, 지정한 필드만 바꿔서 새로운 레코드를 만들어 주는 표현이에요. 불변(immutable) 데이터를 다루는 F#에서 아주 자주 쓰이는 패턴이니 잘 익혀두면 좋아요.

출처: https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/copy-and-update-record-expressions

본문

복사 및 업데이트 레코드 식은 기존 레코드를 복사하고, 지정된 필드를 업데이트한 뒤, 그 결과 레코드를 반환하는 식입니다.

구문 (Syntax)

{ record-name with
    updated-labels }

{| anonymous-record-name with
    updated-labels |}

설명 (Remarks)

레코드와 익명 레코드는 기본적으로 **불변(immutable)**이라서, 이미 만들어진 레코드를 직접 수정할 수 없어요. 그래서 업데이트된 레코드를 만들려면 모든 필드를 처음부터 다시 지정해야 하는 번거로움이 있었죠. 이 불편함을 해결하기 위한 게 바로 복사 및 업데이트 식이에요. 이 식은 기존 레코드를 받아서, 같은 타입의 새 레코드를 만드는데, 지정한 필드는 식에서 주어진 값으로 채우고 나머지 필드는 기존 레코드에서 그대로 가져옵니다.

기존 레코드를 복사하면서 일부 필드 값을 바꿔야 할 때 특히 유용해요.

예를 들어, 아래처럼 새 레코드를 하나 만들었다고 해볼게요.

let myRecord2 =
    { MyRecord.X = 1
      MyRecord.Y = 2
      MyRecord.Z = 3 }

이 레코드에서 두 필드만 바꾸고 싶다면, 복사 및 업데이트 레코드 식을 이렇게 쓰면 됩니다:

let myRecord3 = { myRecord2 with Y = 100; Z = 2 }

중첩 레코드 복사 및 업데이트 (Nested Record Copy and Update)

F# 7.0 이상에서는 복사 및 업데이트 식이 중첩된 레코드 필드의 업데이트를 지원하도록 확장되었어요. 이 기능 덕분에 깊게 중첩된 레코드를 다룰 때 훨씬 간결한 구문을 쓸 수 있게 됐습니다.

다음 예시를 볼까요?

이전 (Before)

type SteeringWheel = { Type: string }
type CarInterior = { Steering: SteeringWheel; Seats: int }
type Car = { Interior: CarInterior; ExteriorColor: string option }

let beforeThisFeature x =
    { x with Interior = { x.Interior with
                            Steering = {x.Interior.Steering with Type = "yoke"}
                            Seats = 5
                        }
    }

이후 (After)

새 기능을 쓰면 **점 표기법(dot-notation)**으로 중첩 필드에 직접 접근해서 바로 업데이트할 수 있어요:

let withTheFeature x =
    { x with Interior.Steering.Type = "yoke"; Interior.Seats = 5 }

이 구문은 여러 개의 with 식을 써야 했던 번거로움을 없애줍니다. 대신 중첩된 필드에 직접 업데이트를 지정하면서도, 같은 식 안에서 서로 다른 중첩 깊이의 여러 필드를 한 번에 업데이트할 수 있어요.

익명 레코드 (Anonymous Records)

이런 구문 확장은 익명 레코드에서도 똑같이 동작해요. 게다가 이 구문을 쓰면 일반 레코드를 익명 레코드로 복사·업데이트하면서, 그 과정에서 새 필드를 추가하는 것도 가능합니다:

let updatedRecord =
    {| originalRecord with
        Interior.Seats = 4;
        Price = 35000 |}

이러한 유연성 덕분에, 일반 레코드를 다루든 익명 레코드를 다루든 동일하게 간결한 구문을 사용할 수 있어요.

더 알아보기 (Learn more)