winitzki / sofp

A free book: "The Science of Functional Programming"
GNU General Public License v2.0
1.41k stars 97 forks source link

Sections 8.3.1 and 8.3.2 #8

Closed greenbergjosh closed 4 years ago

greenbergjosh commented 4 years ago

This may be more of a scala thing, but can you please explain the purpose of the eN vals in the definition

implicit val e1 = eqEither[Int, A] // Instance for Int + A. implicit val e2 = eqPair[A, A] // Instance for A× A. implicit val e3 = eqPair[Int, (A, A)] // Instance for Int× A× A. eqEither[Either[Int, A], (Int, (A, A))]

Section 8.3.1 also follows this pattern, but I can comment all of them out and everything still works for both examples. Was the intent, perhaps, to use them in the final definition of eqEither[Either[Int, A], (Int, (A, A))] in some way? I can't see their purpose otherwise.

Thank you.

winitzki commented 4 years ago

I'd be surprised if you can comment them out. I think these implicits are required to build up the final value. I'll have to run the code again and check.

winitzki commented 4 years ago

Maybe I misunderstood your question. Are you asking about why this code is written at all?

implicit val x = {
  implicit val e1 = some_value
  implicit val e2 = some_other_value
  some_final_value
}

Are you asking what would happen if you delete this entire code?

What I was saying is that you can't delete e1 and e2 in this code, because then some_final_value will not compile due to missing implicits.

greenbergjosh commented 4 years ago

Please drop the following into a scala fiddle

final case class Eq[A](equal: (A, A) => Boolean) 
object Eq { 
  implicit class EqOps[A: Eq](a: A) { 
    def ===(b: A): Boolean = implicitly[Eq[A]].equal(a, b) 
  } 
  // Use type-specific comparisons to define some typeclass instances here: 
  implicit val eqInt: Eq[Int] = Eq[Int](_ == _)
  implicit val eqString: Eq[String] = Eq[String](_ == _) 
  implicit def eqPair[A: Eq, B: Eq] = Eq[(A, B)] { 
    case ((a1, b1), (a2, b2)) => a1 === a2 && b1 === b2 
  }
  implicit def eqEither[A: Eq, B: Eq] = Eq[Either[A, B]] { 
    case (Left(a1), Left(a2)) => a1 === a2 // Compare a1 + 0 and a2 + 0. 
    case (Right(b1), Right(b2)) => (b1 === b2) // Compare 0 + b1 and 0 + b2. 
    case _ => 
      false // a + 0 is never equal to 0 + b. 
  }
}

import Eq._

type S[A] = Either[Either[Int, A], (Int, (A, A))] 
final case class T(s: S[T]) // Recursive type equation T , Int + T + Int×T ×T. 
def eqS[A](implicit ti: Eq[A]): Eq[S[A]] = { // Function of type Eq[A] => Eq[S[A]]. 
  //implicit val e1 = eqEither[Int, A] // Instance for Int + A. 
  //implicit val e2 = eqPair[A, A] // Instance for A× A. 
  //implicit val e3 = eqPair[Int, (A, A)] // Instance for Int× A× A. 
  eqEither[Either[Int, A], (Int, (A, A))] // Instance for Int + A + Int× A× A. 
  } 
implicit def eqT: Eq[T] = Eq { 
  case (T(s1), T(s2)) => eqS(eqT).equal(s1, s2) 
}

val t1 = T(Left(Right(T(Left(Left(10)))))) 
val t2 = T(Left(Right(T(Left(Left(10)))))) 

val t3 = T(Left(Right(T(Right( (5, (T(Left(Left(10))),T(Left(Left(11))))) ))))) 
val t4 = T(Left(Right(T(Right( (5, (T(Left(Left(10))),T(Left(Left(11))))) ))))) 

println(t1===t2)
println(t3===t4)

For me, using scalafiddle.io, this code works. I have exercised both branches of the Either as well as the product. The intermediate eN values are commented out. It appears that they do not need to be explicitly pushed into implicit scope. I believe this is the case because the Eq companion object already contains the generic versions of the subexpressions. Having to explicitly create eN values for each subexpression, where each subexpression is simply a composition of the core expressions (sum, product, etc.) would seem to defeat the purpose of building things this way. We have an Either[A,B], why should I have to explicitly inject an Either[Int,A] into the explicit scope. Wouldn't the Either[A,B] cover that case? The 'some_final_value' seems to be all that is needed, since the 'some_value' and 'some_other_value' are simply composed of primitives we defined in the companion object. Unless I am missing something, I felt that was the whole point. Having to explicitly inject all of the intermediate subexpression types is quite heavy and I hope not necessary. Can you produce an example where the lines I have commented out need to be uncommented?

leobenkel commented 4 years ago

to make it easier: https://scastie.scala-lang.org/7IZJJiwySPWP0lj2GE3ohw , I just copied your code

greenbergjosh commented 4 years ago

Nice to meet you Leo. I am guessing that you prefer that tool. I have a different version of the code in my latest post on issue [https://github.com/winitzki/sofp/issues/9]. I prefer that version, but I am not certain how it is better/worse than the version in this post.

Also, when you say to make it easier, are you referring to something that I should see immediately by looking at that link, or just placing the code there for convenience? Thanks.

leobenkel commented 4 years ago

ah no, just convenience, so the consumers of this thread dont have to copy paste themselves :)

winitzki commented 4 years ago

Thank you for the examples. Unfortunately, I have very little time right now to look in more detail into this. But right now, after a brief look, I can say that indeed I misunderstood how the code worked. All those ev1, ev2 and so on, can be indeed removed from the code, and it will still work.

In my view, those values (ev1, ev2 and so on) are necessary logical steps towards building the final implicit value, and I think it is important to see how they are built one after another, following the steps in the structural analysis of the type. But the Scala compiler can perform these steps automatically, because the compiler has a built-in mechanism for searching for implicits in many steps. If it needs an implicit value of type A, and there is no such value available directly, the compiler will look for an implicit function that converts some other type B to A. If there exists such a function, the compiler will look for an implicit value of type B, and so on. In the examples, the functions eqPair and eqEither are defined as implicit. So, in this way, the Scala compiler can automatically fill in the ev3, ev2, ev1, starting from the required type of the end result and going backwards. I will have to think how to explain all this in the book.

winitzki commented 4 years ago

Now I looked more closely and I find that in your code, you defined eqPair and eqEither as implicit def. I did not do this in the code of the Extractor typeclass; those functions were not defined as implicit. But I did define them as implicit for the Eq typeclass.

As a result, you can remove ev1, ev2, etc., from the code examples of Eq typeclass, but not from the code of the Extractor typeclass as shown in the current version of the book. This is somewhat inconsistent. I did not actually mean to make eqPair and eqEither implicit. I wanted to show how to build an implicit value step by step via structural analysis, and not rely on Scala compiler magic to create a value of a complicated type in one step.