3

I was reading about Diode, and it made me think about lenses in Monocle / Scalaz:

If I (conditionally) modify deeply some part of a deeply nested data-structure using Monocle/Scalaz lens and want to compare if there was a change, is there a need to do a deep comparison, or is there a way to use reference equality to see if the two data-structures (before conditionaly modification and after) are the same ?

In other words:

val f_new=modifyWithMonocleIfTheSunIsUp(f_old)

Can the comparison f_new==f_old be made efficient using reference equality (eq) at the root (f_old, f_new) of the data-structure ?

In other words,

is it true that f_new==f_old is true if and only if f_new.eq(f_old) is true ? (Equation 1)

If not, why not ?

If not, is it possible to make Equation 1 true ? How ?

jhegedus
  • 20,244
  • 16
  • 99
  • 167
  • For my extremely limited experience: if you care about performances, avoid lenses entirely. They slow you down a lot. Anyway the point you raise is interesting, waiting for an answer. – Chobeat Jan 21 '17 at 15:21
  • What is the alternative ? Is the alternative any better ? – jhegedus Jan 21 '17 at 15:34
  • I still have no good answer but believing that lenses are just a form of syntactic sugar with little overhead is a false belief and one is led to think so by how it is sold. It's super good if you need to work with a lot of classes and data structures but for performances, it's not good. But the whole immutable approach is bad for that if you cannot exploit parallelism. I'm interested in alternatives because I'm probably in a scenario similar to your and I would like to know the answer to your question to take inspiration and maybe learn to use lenses more efficiently or to deal with nested DS – Chobeat Jan 21 '17 at 15:38
  • I don't think lenses in Haskell, for example, are bad from a performance point of view, actually they are better than the alternative (which is manual pattern matching). This explains why : https://skillsmatter.com/skillscasts/4251-lenses-compositional-data-access-and-manipulation#video , so probably the same is true for lenses in Scala, I don't think they are any worse than the alternative (which is to use copy). – jhegedus Jan 21 '17 at 16:20
  • Btw, this is another alternative to lenses : http://stackoverflow.com/questions/41201045/normalized-and-immutable-data-model , using REFs . – jhegedus Jan 21 '17 at 16:23
  • Thanks for the link, I will read both. Anyway I'm doing this: http://stackoverflow.com/questions/41750947/handling-heavy-load-on-the-gc-with-scala-case-classes This is to give you context of where I saw the slow down. This thing is already super heavy and stressful for the GC and the processors. With lenses it was like 3x slower, with just a couple of simple lenses. – Chobeat Jan 21 '17 at 16:39
  • How about FRP for a game ? It is meant to solve your problem. – jhegedus Jan 21 '17 at 16:55
  • It was so obvious that I never even considered that. I have some experience with Akka and I believe it really fits my use case. A lot of thinking will be done this night. Thank you very much for the suggestion. – Chobeat Jan 21 '17 at 16:58
  • Here is a nice example of a simple game written with FRP https://www.youtube.com/watch?v=T7XwTolu9YI – jhegedus Jan 22 '17 at 11:56

1 Answers1

1

Maybe you can use modifyF instead of modify to return an Option so you don't have to check if something has changed.

For example :

import monocle.Lens
import monocle.macros.GenLens
import scalaz.std.option._

case class Person(name: String, address: Address)
case class Address(street: String, number: Int)

val addressL: Lens[Person, Address] = GenLens[Person](_.address)
val streetL:  Lens[Address, String] = GenLens[Address](_.street)

val changePersonsStreet: Person => Option[Person] =
  (addressL composeLens streetL).modifyF[Option] { street =>
    // only change street name if first letter comes before 'N'
    street.headOption.filter(_.toString.capitalize < "N").map(_ => "New Street")
    // or any other condition
    // if (theSunIsUp) Some("changed street name") else None
  } _

val alice = Person("Alice", Address("Main Street", 1))

val alice2: Option[Person] = changePersonsStreet(alice)
// Some(Person(Alice,Address(New Street,1)))
// -> modified, no need to check

val alice3 = alice2.flatMap(changePersonsStreet)
// None
// -> not modified
Peter Neyens
  • 9,770
  • 27
  • 33
  • Thanks for the answer, interesting approach. The idea would be to implement fast equality comparison between **any** two `Persons`. So if randomly someone gives me two `Persons` then would need to only look at the references (using `eq`) and if they are equal then I know that the two Persons are the same (no need to do a deep value-by-value comparison). With this approach if someone gives me two random `Option[Person]`-s (which were generated by the program at some point in time - but I don't know when and where) then I cannot be sure if they are the same or not. – jhegedus Jan 22 '17 at 11:51