I have a 3-level nested case class model with a bunch of options that represents some data in a database. It's essentially:
case class User(settings: Option[Settings])
case class Settings(keys: Option[List[KeySet]])
case class KeySet(privateKey: String, publicKey: String)
I understand how to get deeply nested fields out of this using some for comprehension or chains of flatMap
(Scala Option object inside another Option object) and I also understand how to update it using a lens library, but I want to figure out how to update the fields even if some stuff in the tree is None
and automatically make Some
s of those if they don't exist yet.
For example, how would I handle the case where I want to add to the keys
List but the user hasn't yet set any settings
? Is it possible to, in some sense, automatically create a Some(settings)
field and also a Some(keys)
field?
I have an idea of how to do it with a lot of pattern matching, but this seems wrong because of 1. rightward drift of code and 2. not using map
or flatMap
very much with the options.
Is this possible using a lens library on its own? I read here that it might not be possible: https://github.com/julien-truffaut/Monocle/issues/215 as in the case of Monocle it can't update an Option
that is a None
. Maybe I need to think about the problem another way?
Thanks
I'm not sure why you use Option[List[KeySet]]
. Is there an important distinction between None
and an empty List
?
In any case, I find fold
to be a handy tool when working with Options.
def updateUser(u :User, ks :KeySet) :User = {
u.copy(settings =
Some(u.settings.fold(Settings(Some(ks::Nil))) (stngs =>
stngs.copy(keys = Some(stngs.keys.fold(ks::Nil) (ks::_))))))
}
val pat = updateUser(User(None), KeySet("a","b"))
//pat: User = User(Some(Settings(Some(List(KeySet(a,b))))))
val upat = updateUser(pat, KeySet("c","d"))
//upat: User = User(Some(Settings(Some(List(KeySet(c,d), KeySet(a,b))))))