본문 바로가기
Functional Programming

Swift에서의 렌즈(Lens) - Link

by zinozino 2025. 3. 15.

이번엔 더 복잡한 객체를 만들어 볼게요.

 

먼저 렌즈 함수를 다시 볼게요

 

struct Lens<Whole, Part> {
    let view: (Whole) -> Part
    let set: (Part, Whole) -> Whole
}

 

 

스트리머의 채널정보를 알 수 있도록 속성을 하나 더 추가해 볼게요.

struct Channel {
    let subscribers: UInt
    let videos: UInt
}

 

 

 좀 더 복잡한 객체가 되었습니다.

struct Streamer {
    let name: String
    let mainPlatform: String
    let channel: Channel
}

 

 

카더정원이라는 유튜버를 만들었습니다.

let youtuber = Streamer(
    name: "카더정원",
    mainPlatform: "Youtube",
    channel: Channel(subscribers: 835, videos: 20)
)

 

 

이 유튜버의 채널을 들여다보는 Lens를 만들겠습니다.

 

let channelLens = Lens<Streamer, Channel>(
    view: {streamer in streamer.channel},
    set: {
        newChannel,
        streamer in Streamer(
            name: streamer.name,
            mainPlatform: streamer.mainPlatform,
            channel: newChannel
        )
    }
)

 

 

채널을 가져와보겠습니다.

channelLens.view(youtuber)
//Channel(subscribers: 835, videos: 20)

 

 

유튜버의 구독자수가 변경되어 채널 정보가 변경됩니다.

 

여기서 Channel의 구독자가 변경되는 것이니 이것 또한 Lens로 볼 수 있습니다. Part는 자기 자체로는 전체가 될 수 있기 때문입니다.

(Whole) -> Part

let subscriberLens = Lens<Channel, UInt>(
    view: { channel in channel.subscribers},
    set: {
        newSubscriber,
        channel in Channel(
            subscribers: newSubscriber,
            videos: channel.videos
        )
    }
)

 

 

 여기서 생각해 볼 부분은  channelLens Streamer의 Channel까지만 가져오고,

subscrberLens는 Channel에서 Subscrber까지 가져오는데요.

 

이대로 두면 연결이 끊겨있기 때문에 Streamer에서 바로 channe의 Subscriber를 가져올 수 없습니다. 값을 경신할 수도 없습니다.

 

그래서 저는 이 간극을 이어주는 함수하나를 만들어 보도록 하겠습니다.

 

 

표현하자면 이렇게 되겠네요.

=> (Whole) -> Part ⛓️‍💥 (Part) -> PartOfPart

=> (Whole) -> Part ⛓️ (Part) -> PartOfPart => (Whole) -> PartOfPart ✅

 

함수의 시그니처는 이렇게 되겠네요.

func link<Whole, Part, PartOfPart>(_ wholeLens: Lens<Whole, Part>, _ partLens: Lens<Part, PartOfPart>)
-> Lens<Whole, PartOfPart>

 

 

구현해 보도록 하겠습니다.

 

func link<Whole, Part, PartOfPart>(_ wholeLens: Lens<Whole, Part>, _ partLens: Lens<Part, PartOfPart>) -> Lens<Whole, PartOfPart> {
    let linkedLens: Lens<Whole, PartOfPart> = .init(
        view: {whole in
            let part = wholeLens.view(whole) //wholeLens는 whole을 넣으면 part를 반환합니다.
            let partOfPart = partLens.view(part) //partLens는 part을 넣으면 partOfPart를 반환합니다.
            return partOfPart
        },
        set: { partOfPart, whole in
            let part = wholeLens.view(whole) // partOfPart를 먼저 Part에 넣기위해서 wholeLens를 통해서 Part를 반환합니다.
            let newPart = partLens.set(partOfPart, part) // PartLens로 part에 partOfpart를 갱신시키고 반환합니다.
            let newWhole = wholeLens.set(newPart, whole) // 반환받은 Part를 wholeLens통해서 Whole의 Part를 갱신시키고 반환합니다.
            return newWhole
        }
    )
    
    return linkedLens
}

 

 

이제 끊긴 channelLens과 subscriberLens를 연결시켜 보도록 하겠습니다.

let linkedToSubscriber = link(channelLens, subscriberLens)

 

 

구독자수를 가져와보도록 하겠습니다.

let subscriberOfYoutube = linkedToSubscriber.view(youtuber)
print("youtuber의 구독자수는 \(subscriberOfYoutube)") //835

 

 

구독자수가 늘어서 구독자수를 증가시키겠습니다.

let updatedSubscriberOfYoutuber = linkedToSubscriber.set(1000, youtuber)
print(updatedSubscriberOfYoutuber)
//Streamer(name: "카더정원", mainPlatform: "Youtube", channel: Channel(subscribers: 1000, videos: 20))

 

이번 글은 Lens를 이어주는 함수를 만들어보고, Lens기능을 수직방향으로 확장시켰다고 볼 수 있는데요. 

다음 글은 수평적인 방향으로 사용할 수 있도록 해볼게요.

 

감사합니당👐

 

 

 

 

참조

 

https://medium.com/javascript-scene/lenses-b85976cb0534

 

 

Lenses

Composable Getters and Setters for Functional Programming

medium.com

https://bartoszmilewski.com/2014/10/28/category-theory-for-programmers-the-preface/

 

Category Theory for Programmers: The Preface

Table of Contents Part One Category: The Essence of Composition Types and Functions Categories Great and Small Kleisli Categories Products and Coproducts Simple Algebraic Data Types Functors Functo…

bartoszmilewski.com

 

'Functional Programming' 카테고리의 다른 글

Swift에서의 렌즈(Lens) - Basic  (0) 2025.03.13