Skip to content
This repository has been archived by the owner on Feb 8, 2022. It is now read-only.

add implicit ADT for skipping serialization tests #129

Closed

Conversation

sritchie
Copy link
Contributor

@sritchie sritchie commented Dec 9, 2015

First stab at #123.

case object SkipSerialization extends SerializationCheck

object SerializationCheck {
implicit val check: SerializationCheck = SkipSerialization
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we want to not check by default.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, my bad. Pushed early.

@sritchie
Copy link
Contributor Author

@johnynek changed the name and added the type parameter. Do we still want to thread implicit params through all of the test methods if we're only supporting an actual definition of IsSerializable[T] inside the companion object? The only reason to do that is if we want to allow scoping inside the tests. Might be good to just leave it alone.

"serializable" -> (implicitly[IsSerializable[M]] match {
case Is() =>
Prop(_ => Result(status = Proof))
case IsNot() =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we only be checking the serializability if it Is serializable instead of if it IsNot? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh, sorry about that. Second time I goofed those cases. Fixed.

def serializable[M](m: M): (String, Prop) = "serializable" -> Prop { _ =>
Result(status = Proof)
}
def serializable[M: IsSerializable](m: M): (String, Prop) =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a little bit confusing to me, because it looks like M must be serializable in order to call this, when in fact the IsSerializable instance could be IsNot. I'm at a loss for names that seem any better though.

@ceedubs
Copy link
Contributor

ceedubs commented Dec 10, 2015

I'm not sure I quite understand #129 (comment). I wouldn't think that this approach would require the instance to be defined in the companion object. Would it? In most cases I don't think we'll have the instance in the companion object because 1) Future is the instance where we've wanted this and 2) tests are probably more likely to rely on the laws module than the main code where classes are defined.

@johnynek
Copy link
Contributor

I think we have to thread the implicit all the way through to take this approach (even if there is an IsSerializable instance for the type) due to type erasure.

I haven't really looked at the hierarchy to see if that will be a disaster.

I agree now with @ceedubs even though I suggested the name. The problem is this is purely type-level function. We aren't going to do anything with it except skip a test at runtime. One way out of the naming thing would be to use Priority. We could do implicit p: Priority[DummyImplicit, NotSerializable[M]] and then only define NotSerializable[Future[T]] for instance. But really, that's just another way to express the ADT in a possibly more indirect way.

I would suggest, to see if this approach works, we set aside the naming question and see how clean it is to plumb it all the way through and have a test for a non-serializable class (maybe something that explicitly throws if it is serialized).

@ceedubs
Copy link
Contributor

ceedubs commented Dec 10, 2015

@johnynek I think that's a good suggestion. And I now understand why you were saying it would need to be threaded all the way through. I had forgotten that other tests call the serializability test, but that's of course why this is needed in the first place :)


def additiveCommutativeSemigroup(implicit A: AdditiveCommutativeSemigroup[A],
check: IsSerializable[CommutativeGroup[A]],
addCheck: IsSerializable[AdditiveCommutativeGroup[A]]) =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lots of required implicits.

@sritchie
Copy link
Contributor Author

@johnynek, @ceedubs - take a look and see if you guys can stomach the required changes in LawTests. Before we were explicitly providing implicit parameters. Now that there are two or three per method, I expanded functions out so I could use a locally defined implicit and let the default resolution handle the rest.

Also - it looks like to reuse these tests, users are going to extend LawTestsBase. Currently that means they inherit all of the algebra tests, in addition to the helpers. Do we want to move the actual tests out into their own trait, separate from laws etc?

@@ -0,0 +1,17 @@
package algebra.laws

sealed trait IsSerializable[+T]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be covariant? I don't think so. For instance, if Int has a given IsSerialializable instance, I can't in general use that same thing for Any.

On the other hand, if I want to test a given type M1 which I know is a subclass of M2, then if I have a Gen[M1] that I use for all the tests for M2 (note Gen is covariant), then in that case the IsSerializable instance will work to "upcast". I don't immediately see how to formalize that in the type system. Maybe something like:

  def upcast[M2, M1 <: M2](g: Gen[M1], isser: IsSerializable[M1]): (Gen[M2], IsSerializable[M2]) =
  (g, isser.asInstanceOf[IsSerializable[M2]])

But that is only safe if you use the IsSerializable instance with the Gen. It doesn't really glue them together.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that this shouldn't be covariant.

@johnynek
Copy link
Contributor

I really appreciate you running this idea down, @sritchie . @ceedubs What do you think? do you have any other ideas? I guess a default parameter that is threaded through is the other idea. Is there any fancy scalacheck trick that we could change an argument within a scope at the scalacheck level so we don't have to litter the code with threading all the way through (that may be ugly in its own way).

@sritchie
Copy link
Contributor Author

Annoying that we can't just add a method at the bottom of the trait heirarchy (inside GroupLaws) that supplies the default implicit, and remove it from the IsSerializable companion object.

If we do that, will any trait that extends the base and overrides that method also pass its new default implicit on to calls to serializable?

Then users can just make a new class with an overridden def for types that can't support serialization. Happy to try it. (My mental ability to anticipate implicit scoping's still rusty :)

@sritchie
Copy link
Contributor Author

Ah, I guess not, since LawTestsBase doesn't extend that stack.

@sritchie
Copy link
Contributor Author

Let's close this one up for now. The changes required for this approach are insane. Let me know what you think of the simpler approach here:

#141

@sritchie sritchie closed this Dec 26, 2015
@sritchie sritchie deleted the feature/skip_serialization branch December 26, 2015 23:20
@sritchie sritchie restored the feature/skip_serialization branch December 26, 2015 23:20
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants