Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A mostly clean room reimplementation of Or. #36

Merged
merged 9 commits into from
Feb 4, 2015

Conversation

rossabaker
Copy link
Member

Implements feedback from #11. Specifically:

  1. LOr and ROr move to Or.LeftOr and Or.RightOr.
  2. Constructors are still public, but left and right preserved in
    OrFunctions to infer Or. Use what you like.
  3. Only the Scaladoc is ripped from Scalaz.

Also tries to adhere to the emerging consensus on #27.

Preliminary benchmarking showed monomorphism is faster than polymorphism,
and that implementing other operations using fold/bimap is immeasurable.
The second finding is dubious with respect to allocations. If someone
can prove these guilty via benchmark, we can revisit.

I'd like to add tests after #29 happens.

Fixes #11.

Implements feedback from typelevel#11.  Specifically:
1. LOr and ROr move to `Or.LeftOr` and `Or.RightOr`.
2. Constructors are still public, but `left` and `right` preserved in
   `OrFunctions` to infer `Or`.  Use what you like.
3. Only the Scaladoc is ripped from Scalaz.

Also tries to adhere to the emerging consensus on typelevel#27.

Preliminar benchmarking showed monomorphism is faster than polymorphism,
and that implementing other operations using fold/bimap is immeasurable.
The second finding is dubious with respect to allocations.  If someone
can prove these guilty via benchmark, we can revisit.

I'd like to add tests after typelevel#29 happens.
@non
Copy link
Contributor

non commented Feb 2, 2015

This looks great!

If you wouldn't mind adding toOption and toEither I think most users would appreciate having those available. I think the generic to method is good too, but for those common interop cases I think it's nice to have explicit methods.

case LOr(_) => left
case ROr(_) => r
}
sealed abstract class Or[+A, +B] {
Copy link
Member

Choose a reason for hiding this comment

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

Suggest always doing this for ADTs to get a better LUB when unifying constructors.

sealed abstract class Or[+A, +B] extends Product with Serializable {

Copy link
Contributor

Choose a reason for hiding this comment

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

@tpolecat do you have an example of the benefit where it is helpful to add extends Product with Serializable ?

Copy link
Contributor

Choose a reason for hiding this comment

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

scalaz/scalaz#450

For abstract classes at the root of ADTs, I've added these silly supers to the root. Here's why. It's even worse than that: the inferred types look like Product with Serializable with Box[B], and they erase to Object, not Box[B], in the stored Java types.
The benefit is improved inference when using case classes explicitly; the drawback is that Product methods appear in the type itself. So keep this, remove it, whichever you like.

Copy link
Member

Choose a reason for hiding this comment

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

@julien-truffaut thus:

sealed trait A
case class Aa() extends A
case class Ab() extends A

scala> List(Aa(), Ab())
res1: List[Product with Serializable with A] = List(Aa(), Ab()) 

sealed trait A extends Product with Serializable
case class Aa() extends A
case class Ab() extends A

scala> List(Aa(), Ab())
res2: List[A] = List(Aa(), Ab()) // LUB is better

Copy link
Contributor

Choose a reason for hiding this comment

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

@tpolecat cool I didn't know this trick!

Copy link
Contributor

Choose a reason for hiding this comment

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

I was sitting next to @tpolecat 3 days ago when we both learned this trick during Jon Pretty's nescala talk ;)

@non
Copy link
Contributor

non commented Feb 2, 2015

@tpolecat Those are great suggestions. I keep mentioning this contributor's guide that doesn't exist, but those would be great things to add to it somewhere.

new Eq[A Or B] {
def eqv(a1: A Or B, a2: A Or B) =
a1 === a2
implicit class MergeableOr[A](private val self: A Or A) extends AnyVal {
Copy link
Member

Choose a reason for hiding this comment

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

This is interesting. Why is this is done with syntax rather than a method with a type constraint? i.e., something like

def merge[X >: A](implicit ev: B <:< X]): X = ...

Apologies if I missed an earlier discussion about this.

Copy link
Contributor

Choose a reason for hiding this comment

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

scalaz/scalaz#580 discussion in scalaz

@non
Copy link
Contributor

non commented Feb 2, 2015

This looks good. 👍

One thing that we did in algebra that I'd like to try to do here is to ensure our type class instances are Serializable. This is a godsend to anyone who wants to write spark or Hadoop jobs and might end up capturing type class instances.

I think we should merge this PR before doing that, but I just wanted to mention it here.

@stew
Copy link
Contributor

stew commented Feb 2, 2015

yeah I agree on the serializable stuff, that's coming up for us with spark.

@julien-truffaut
Copy link
Contributor

concretely what does it mean? Do we simply need to extends all TC with Serializable?

@non
Copy link
Contributor

non commented Feb 2, 2015

Yes, and the instances need to do this as well.

I actually have a rule for law-checking which confirms an instances is serializable:

https://github.com/non/algebra/blob/master/laws/src/main/scala/algebra/laws/Rules.scala#L12

@tpolecat
Copy link
Member

tpolecat commented Feb 2, 2015

Yep if it matters then it needs to be part of the base test for all typeclasses; Serializable is just a marker of intent, it doesn't prove anything.

@mpilquist
Copy link
Member

Should simulacrum do this automatically? I was already considering making simulacrum change all type class traits to universal traits.

@non
Copy link
Contributor

non commented Feb 2, 2015

@mpilquist If you are open to it then I think it would be great. I don't see a good reason anyone should specifically want their type classes to be non-serializable.

(But of course we still have to remember to extend serializable in the instances too.)

@julien-truffaut
Copy link
Contributor

pardon the stupid question but why do you need the instance to extends Serializable if the TC already does?

@non
Copy link
Contributor

non commented Feb 2, 2015

@julien-truffaut I'm not sure 100% sure why but I think the Serializable trait is somewhat special and functions a little bit like an annotation. Maybe someone else here has a better understanding, but I know that until algebra instances extended it themselves they were failing the test I linked.

(Also if someone can demonstrate it working the "normal" way then that would be ideal. I'm just reporting my own experience -- I may have messed something up.)

@tpolecat
Copy link
Member

tpolecat commented Feb 2, 2015

Huh. In Java you can inherit it, but maybe it gets squashed out in the trait encoding?

implicit def OrShow[A, B](implicit showA: Show[A], showB: Show[B]): Show[A Or B] =
Show.show[A Or B](_.fold(a => s"LOr(${showA.show(a)})", b => s"ROr(${showB.show(b)})"))
object Or extends OrInstances with OrFunctions {
final case class LeftOr[+A](a: A) extends (A Or Nothing)
Copy link
Contributor

Choose a reason for hiding this comment

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

do we need the Or suffix on these names given they are namespaced by the Or object already?

Copy link
Contributor

Choose a reason for hiding this comment

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

It is convenient when the name of the class is unique when you use auto import

Copy link
Member

Choose a reason for hiding this comment

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

Agree. It's really easy to run into trouble when you're shadowing names that are already in scope by default. Common point of bafflement in #scala for people in the Coursera course, which has exercises to implement List and Set.

case ROr(_) => r
}
def fold[C](fa: A => C, fb: B => C): C = this match {
case RightOr(b) => fb(b)
Copy link
Contributor

Choose a reason for hiding this comment

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

it is really a details but I think it reads slightly better if you pattern match first with LeftOr since the first function is on A

Copy link
Member Author

Choose a reason for hiding this comment

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

This was an almost certainly premature optimization, figuring there'd be more rights than lefts. I'll flip them for readability.

@julien-truffaut
Copy link
Contributor

can you add a show method? and implement Show instance in terms of it

@non
Copy link
Contributor

non commented Feb 4, 2015

I think we should prefer new Show[...] { ... } instead of Show.show[...](...) in cats.

The reason is that while both allocate Show instances the latter also creates an unnecessary Function1 instance. Maybe it's a bit premature but if we're going to have a standard I'd prefer to use this one.

@julien-truffaut
Copy link
Contributor

@non good for me, we should also add this to the contributor guide.

@non
Copy link
Contributor

non commented Feb 4, 2015

I think I'm pretty happy with this. 👍

Anyone know a reason why we shouldn't merge this and continue future work in another PR?

@mpilquist
Copy link
Member

👍

non added a commit that referenced this pull request Feb 4, 2015
A mostly clean room reimplementation of Or.
@non non merged commit 16fafd9 into typelevel:master Feb 4, 2015
@non non removed the in progress label Feb 4, 2015
@rossabaker rossabaker deleted the topic/or-rewrite branch February 5, 2015 00:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Revisit the Or datatype
9 participants