Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ネストしたdata classの面倒な更新にさようなら!Lensを作って理解するArrowのO...

Avatar for shiita0903 shiita0903
November 01, 2025

ネストしたdata classの面倒な更新にさようなら!Lensを作って理解するArrowのOpticsの世界

Kotlin Fest 2025で発表したスライドです。
https://2025.kotlinfest.dev/

Avatar for shiita0903

shiita0903

November 01, 2025
Tweet

More Decks by shiita0903

Other Decks in Programming

Transcript

  1. ෳࡶͳEBUBDMBTTมߋͷਏ͞ data class User(val id: Int, val profile: Profile) data

    class Profile(val name: String, val statusMessage: StatusMessage) data class StatusMessage(val text: String, val updatedTime: Long) fun capitalizeStatusMessage(user: User): User = user.copy( profile = user.profile.copy( statusMessage = user.profile.statusMessage.copy( text = user.profile.statusMessage.text.uppercase() ) ) )
  2. -FOTʹΑΔਏ͞ͷܰݮ fun capitalizeStatusMessage(user: User): User = user.copy( profile = user.profile.copy(

    statusMessage = user.profile.statusMessage.copy( text = user.profile.statusMessage.text.uppercase() ) ) ) fun capitalizeStatusMessageWithLens(user: User): User = User.profile.statusMessage.text.modify(user) { it.uppercase() }
  3. -FOTΛ࢖͏ // Get fun getStatusMessage(user: User): String = User.profile.statusMessage.text.get(user) //

    Set fun updateStatusMessage(user: User, newMessage: String): User = User.profile.statusMessage.text.set(user, newMessage) // Modify(Getter + Setter) fun capitalizeStatusMessage(user: User): User = User.profile.statusMessage.text.modify(user) { it.uppercase() }
  4. -FOTͷ࢓૊ΈΛཧղ͢Δ interface PLens<S, T, A, B> : POptional<S, T, A,

    B> { fun get(source: S): A override fun set(source: S, focus: B): T . . . } w"SSPXLUͰͷ-FOTͷ࣮૷
  5. (FUUFSͷ࡞੒ fun interface Getter<S, A> { fun get(source: S): A

    } data class User(val id: Int, val profile: Profile) fun getProfile(user: User): Profile { val profileGetter = Getter<User, Profile> { source -> source.profile } return profileGetter.get(user) } w4(FUUFSͷιʔεͷܕ 6TFS  w"(FUUFSͷϑΥʔΧεͷܕ 1SP fi MF
  6. (FUUFSͷ߹੒ fun interface Getter<S, A> { fun get(source: S): A

    operator fun <B> plus(other: Getter<in A, out B>): Getter<S, B> = TODO() } data class User(val id: Int, val profile: Profile) data class Profile(val name: String, val statusMessage: StatusMessage) fun getStatusMessage(user: User): StatusMessage { val profileGetter = Getter<User, Profile> { source -> source.profile } val statusMessageGetter = Getter<Profile, StatusMessage> { source - > source.statusMessage } val composedGetter: Getter<User, StatusMessage> = profileGetter + statusMessageGetter return composedGetter.get(user) }
  7. (FUUFSͷ߹੒ fun interface Getter<S, A> { fun get(source: S): A

    operator fun <B> plus(other: Getter<in A, out B>): Getter<S, B> = Getter { source: S -> val a: A = get(source) other.get(a) } }
  8. 4FUUFSͷ࡞੒ fun interface Setter<S, A> { fun modify(source: S, map:

    (A) -> A): S fun set(source: S, focus: A): S = modify(source) { _: A -> focus } } data class User(val id: Int, val profile: Profile) fun setProfile(user: User, newProfile: Profile): User { val profileSetter = Setter<User, Profile> { source, map -> val modifiedProfile = map(source.profile) source.copy(profile = modifiedProfile) } return profileSetter.set(user, newProfile) }
  9. 4FUUFSͷ߹੒ fun interface Setter<S, A> { fun modify(source: S, map:

    (A) -> A): S fun set(source: S, focus: A): S = modify(source) { _: A -> focus } operator fun <B> plus(other: Setter<A, B>): Setter<S, B> = Setter { source: S, map: (B) -> B -> modify(source) { focus: A - > other.modify(focus, map) } } }
  10. 4FUUFSͷ߹੒ data class User(val id: Int, val profile: Profile) data

    class Profile(val name: String, val statusMessage: StatusMessage) fun setStatusMessage(user: User, newStatusMessage: StatusMessage): User { val profileSetter = Setter<User, Profile> { source, map -> val modifiedProfile = map(source.profile) source.copy(profile = modifiedProfile) } val statusMessageSetter = Setter<Profile, StatusMessage> { source, map -> val modifiedStatusMessage = map(source.statusMessage) source.copy(statusMessage = modifiedStatusMessage) } val composedSetter: Setter<User, StatusMessage> = profileSetter + statusMessageSetter return composedSetter.set(user, newStatusMessage) }
  11. -FOTͷ࡞੒ class Lens<S, A>( private val getter: Getter<S, A>, private

    val setter: Setter<S, A> ) : Getter<S, A> by getter, Setter<S, A> by setter { operator fun <B> plus(other: Lens<A, B>): Lens<S, B> = Lens( getter = this.getter + other.getter, setter = this.setter + other.setter ) }
  12. ࡞੒ͨ͠-FOTͷར༻ data class User(val id: Int, val profile: Profile) data

    class Profile(val name: String, val statusMessage: StatusMessage) data class StatusMessage(val text: String, val updatedTime: Long) fun createStatusMessageTextLens(): Lens<User, String> { val profileLens = Lens<User, Profile>( getter = { source -> source.profile }, setter = { source, map -> source.copy(profile = map(source.profile)) } ) val statusMessageLens = Lens<Profile, StatusMessage>( getter = { source -> source.statusMessage }, setter = { source, map -> source.copy(statusMessage = map(source.statusMessage)) } ) val textLens = Lens<StatusMessage, String>( getter = { source -> source.text }, setter = { source, map -> source.copy(text = map(source.text)) } ) return profileLens + statusMessageLens + textLens }
  13. ࡞੒ͨ͠-FOTͷར༻ data class User(val id: Int, val profile: Profile) data

    class Profile(val name: String, val statusMessage: StatusMessage) data class StatusMessage(val text: String, val updatedTime: Long) fun createStatusMessageTextLens(): Lens<User, String> { … } fun myLens(user: User, newMessage: String) { val lens = createStatusMessageTextLens() val text: String = lens.get(user) val newUser1: User = lens.set(user, newMessage) val newUser2: User = lens.modify(user) { it.uppercase() } }
  14. ࡞੒ͨ͠-FOTͷܽ఺ fun createStatusMessageTextLens(): Lens<User, String> { val profileLens = Lens<User,

    Profile>( getter = { source -> source.profile }, setter = { source, map -> source.copy(profile = map(source.profile)) } ) val statusMessageLens = Lens<Profile, StatusMessage>( getter = { source -> source.statusMessage }, setter = { source, map -> source.copy(statusMessage = map(source.statusMessage)) } ) val textLens = Lens<StatusMessage, String>( getter = { source -> source.text }, setter = { source, map -> source.copy(text = map(source.text)) } ) return profileLens + statusMessageLens + textLens }
  15. ࡞੒ͨ͠-FOTͷܽ఺ fun createStatusMessageTextLens(): Lens<User, String> { val profileLens = Lens<User,

    Profile>( getter = { source -> source.profile }, setter = { source, map -> source.copy(profile = map(source.profile)) } ) val statusMessageLens = Lens<Profile, StatusMessage>( getter = { source -> source.statusMessage }, setter = { source, map -> source.copy(statusMessage = map(source.statusMessage)) } ) val textLens = Lens<StatusMessage, String>( getter = { source -> source.text }, setter = { source, map -> source.copy(text = map(source.text)) } ) return profileLens + statusMessageLens + textLens } fun createStatusMessageTextLensByArrow(): Lens<User, String> = User.profile.statusMessage.text
  16. -FOTͷͨΊͷίʔυੜ੒ @optics data class Profile(val name: String, val statusMessage: StatusMessage)

    { companion object } w!PQUJDTΛEBUBDMBTTʹ͚ͭΔ wۭͷDPNQBOJPOPCKFDUΛఆٛ͢Δ
  17. -FOTͷͨΊͷίʔυੜ੒ @optics data class Profile(val name: String, val statusMessage: StatusMessage)

    { companion object } / / Generated code val Profile.Companion.name: Lens<Profile, String> get() = Lens( get = { profile: Profile -> profile.name }, set = { profile: Profile, value: String -> profile.copy(name = value) } ) val Profile.Companion.statusMessage: Lens<Profile, StatusMessage> get() = Lens( get = { profile: Profile -> profile.statusMessage }, set = { profile: Profile, value: StatusMessage - > profile.copy(statusMessage = value) } ) wDPNQBOJPOPCKFDUͷ֦ுϓϩύςΟܦ༝Ͱɺੜ੒͞Εͨ -FOT͕ར༻Մೳ
  18. -FOTͷͨΊͷίʔυੜ੒ val composedLens: Lens<User, StatusMessage> = User.profile.statusMessage // Generated code

    val <S> Lens<S, Profile>.statusMessage: Lens<S, StatusMessage> inline get() = this + Profile.statusMessage val User.Companion.profile: Lens<User, Profile> inline get() = Lens( get = { user: User -> user.profile }, set = { user: User, value: Profile -> user.copy(profile = value) } ) val Profile.Companion.statusMessage: Lens<Profile, StatusMessage> inline get() = Lens( get = { profile: Profile -> profile.statusMessage }, set = { profile: Profile, value: StatusMessage - > profile.copy(statusMessage = value) } )
  19. -FOTͷͨΊͷϢʔςΟϦςΟؔ਺ val modifiedUser1 = User.profile.name.modify(user) { it.uppercase() } val modifiedUser2

    = User.profile.statusMessage.text .modify(modifiedUser1) { it.uppercase() } val result = User.profile.statusMessage.updatedTime .modify(modifiedUser2) { System.currentTimeMillis() } wෳ਺ͷϓϩύςΟͷҰׅมߋ͸ۤख
  20. -FOTͷͨΊͷϢʔςΟϦςΟؔ਺ val result = user.copy { User.profile.name transform { it.uppercase()

    } inside(User.profile.statusMessage) { StatusMessage.text transform { it.uppercase() } StatusMessage.updatedTime set System.currentTimeMillis() } } wDPQZ Ͱ-FOTΛΑΓศརʹ࢖͑Δ wJOTJEF ͸ಛఆͷϑΥʔΧε΁ͷγϣʔτΧοτ
  21. -FOTͷͨΊͷϢʔςΟϦςΟؔ਺ val modifiedUser1 = User.profile.name.modify(user) { it.uppercase() } val modifiedUser2

    = User.profile.statusMessage.text .modify(modifiedUser1) { it.uppercase() } val result = User.profile.statusMessage.updatedTime .modify(modifiedUser2) { System.currentTimeMillis() } val result = user.copy { User.profile.name transform { it.uppercase() } inside(User.profile.statusMessage) { StatusMessage.text transform { it.uppercase() } StatusMessage.updatedTime set System.currentTimeMillis() } }
  22. ଞϥΠϒϥϦͱͷ࿈ܞ val messageState = remember { mutableStateOf(message) } val messageStateFlow

    = MutableStateFlow(message) messageState.updateCopy { StatusMessage.text transform { it.uppercase() } } messageStateFlow.updateCopy { StatusMessage.text set "newText" } wDPQZ ͸.VUBCMF4UBUF΍.VTUBCMF4UBUF'MPXͰ΋ར༻Մೳ
  23. ଞϥΠϒϥϦͱͷ࿈ܞ val messageState = remember { mutableStateOf(message) } val messageStateFlow

    = MutableStateFlow(message) val messageSharedFlow = MutableSharedFlow<StatusMessage>() val textState: State<String> = messageState.optic(StatusMessage.text) val textStateFlow: StateFlow<String> = messageStateFlow.optic(StatusMessage.text) val textSharedFlow: SharedFlow<String> = messageSharedFlow.optic(StatusMessage.text) wPQUJD ͸-FOTΛ࢖ͬͯ৽͍͠4UBUF΍'MPXΛੜ੒
  24. -FOTͷݶք data class User( val id: Int, val profile: Profile,

    val friends: Map<Int, User> ) fun getUserFriendProfile(user: User, friendUserId: Int): Profile? = User.friends. ??? .get(user) w-FOT͸EBUBDMBTTΛѻ͏ۜͷ஄ؙͰ͸ͳ͍
  25. -FOTͷݶք data class User(val id: Int, val profile: Profile) sealed

    class Profile { abstract val name: String abstract val statusMessage: StatusMessage data class BasicProfile( override val name: String, override val statusMessage: StatusMessage) : Profile() data class PremiumProfile( override val name: String, override val statusMessage: StatusMessage, val nameDecorationMetadata: String) : Profile() } fun maybeSetNameDecorationMetadata(user: User, newMetadata: String): User = User.profile. ??? .set(user, newMetadata) w-FOT͸EBUBDMBTTΛѻ͏ۜͷ஄ؙͰ͸ͳ͍
  26. 0QUJDT w"SSPXLUͷ0QUJDT͸ҎԼͷͭͰߏ੒͞Ε͍ͯΔ w 5SBWFSTBM w 0QUJPOBM w -FOT w 1SJTN

    w *TP wԼʹߦ͘΄Ͳೳྗ͕૿͑Δ͕ ద༻Մೳൣғ͸ڱ·Δ IUUQTBSSPXLUJPMFBSOJNNVUBCMFEBUBJOUSPNBOZPQUJDTUPSVMFUIFNBMM
  27. 5SBWFSTBM interface Traversal<S, A> { fun modify(source: S, map: (focus:

    A) -> A): S } w-JTUͳͲɺෳ਺ݸͷ஋ͷߋ৽͕Ͱ͖Δ wLPUMJODPMMFDUJPOTNBQΛҰൠԽͨ͠ɺNPEJGZ ͕ར༻Մೳ
  28. 5SBWFSTBM data class UserGroup(val name: String, val users: List<User>) data

    class Profile(val name: String, val statusMessage: StatusMessage) data class StatusMessage(val text: String, val updatedTime: Long) fun updateAllUpdatedTime(group: UserGroup): UserGroup = UserGroup.users.modify(group) { user - > user.map { User.profile.statusMessage.updatedTime.set(it, System.currentTimeMillis()) } } fun updateAllUpdatedTimeWithTraversal(group: UserGroup): UserGroup = UserGroup.users.every.profile.statusMessage.updatedTime .set(group, System.currentTimeMillis()) wNBQ Λ5SBWFSTBMͰஔ͖׵͑Δ
  29. 0QUJPOBM interface Optional<S, A> : Traversal<S, A> { override fun

    modify(source: S, map: (focus: A) -> A): S fun getOrNull(source: S): A? } w-JTU΍.BQͳͲͷίϨΫγϣϯ͔ΒҰͭͷ஋ΛऔΓग़ͤΔ wHFU0S/VMM Ͱࣦഊ͢ΔՄೳੑͷ͋ΔॲཧΛදݱՄೳ
  30. 0QUJPOBM data class User( val id: Int, val profile: Profile,

    val friends: Map<Int, User> ) fun getUserFriendProfile(user: User, friendUserId: Int): Profile? { val friendOptional: Optional<User, User> = User.friends.index(friendUserId) return friendOptional.profile.getOrNull(user) } w.BQ, 7Λ0QUJPOBMͰѻ͏
  31. 1SJTN interface Prism<S, A> : Optional<S, A> { override fun

    modify(source: S, map: (focus: A) -> A): S override fun getOrNull(source: S): A? fun reverseGet(focus: A): S } wTFBMFEDMBTTͷΫϥε֊૚ͷදݱʹ࢖ΘΕΔ wSFWFSTF(FU ͰϑΥʔΧε͔Βιʔε͕ߏஙՄೳ
  32. 1SJTN sealed class Profile { data class BasicProfile() : Profile()

    data class PremiumProfile() : Profile() } val basicProfilePrism: Prism<Profile, Profile.BasicProfile> = Profile.basicProfile val premiumProfilePrism: Prism<Profile, Profile.PremiumProfile> = Profile.premiumProfile wαϒΫϥεͷΫϥε໊ͷ1SJTN͕ੜ੒͞ΕΔ
  33. 1SJTN data class User(val id: Int, val profile: Profile) sealed

    class Profile { abstract val name: String abstract val statusMessage: StatusMessage data class BasicProfile( override val name: String, override val statusMessage: StatusMessage) : Profile() data class PremiumProfile( override val name: String, override val statusMessage: StatusMessage, val nameDecorationMetadata: String) : Profile() } fun maybeSetNameDecorationMetadata(user: User, newMetadata: String): User = User.profile.premiumProfile.nameDecorationMetadata.set(user, newMetadata) wTFBMFEDMBTTΛ1SJTNͰѻ͏
  34. -FOT interface Lens<S, A> : Optional<S, A> { override fun

    modify(source: S, map: (focus: A) -> A): S fun get(source: S): A } w(FUUFSͱ4FUUFS͕ར༻Ͱ͖Δ wHFU ͰϑΥʔΧεΛ࣮֬ʹऔಘՄೳ
  35. *TP interface Iso<S, A> : Prism<S, A>, Lens<S, A> {

    override fun modify(source: S, map: (focus: A) -> A): S override fun reverseGet(focus: A): S override fun get(source: S): A } wͭͷܕͷؒͰɺ৘ใͷଛࣦແ͠ͷ૬ޓม׵͕Մೳ w-FOTͱ1SJTNͷ྆ํͷೳྗΛ࣋ͭ
  36. *TP data class StatusMessage(val text: String, val updatedTime: Long) val

    statusMessageToPairIso: Iso<StatusMessage, Pair<String, Long >> = Iso( get = { it.text to it.updatedTime }, reverseGet = { StatusMessage(it.first, it.second) } ) @JvmInline value class UserId(val id: Int) fun UserId.getValue(): Int = UserId.id.get(this) fun Int.toUserId(): UserId = UserId.id.reverseGet(this) w*TPʹΑΔܕม׵
  37. 0QUJDT·ͱΊ 0QUJDTaؔ਺ NPEJGZ HFU0S/VMM SFWFSTF(FU HFU 5SBWFSTBM ̋ ✕ ✕

    ✕ 0QUJPOBM ̋ ̋ ✕ ✕ 1SJTN ̋ ̋ ̋ ✕ -FOT ̋ ̋ ✕ ̋ *TP ̋ ̋ ̋ ̋