Last active
April 11, 2018 21:39
-
-
Save devshorts/32f216295d1acf064863d919a51e2c8b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Encoding for "A is not a subtype of B" | |
* @note original credit: https://gist.github.com/milessabin/c9f8befa932d98dcc7a4 | |
*/ | |
trait NotTypeOf[A, B] | |
trait NotTypeImplicits { | |
// Uses ambiguity to rule out the cases we're trying to exclude | |
implicit def allowedType[A, B] : A NotTypeOf B = null | |
implicit def typeSuperTypeOfInvalid[A, B >: A] : A NotTypeOf B = null | |
implicit def typeSuperTypeOfInvalidAmbiguous[A, B >: A] : A NotTypeOf B = null | |
} | |
object NotTypeImplicits extends NotTypeImplicits |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.scalatest.{FlatSpec, Matchers} | |
class NotTypeTest extends NotTypeImplicits { | |
def isNotType[T : Manifest: Not[Long]#Evidence](): Boolean = manifest[T].runtimeClass != classOf[Long] | |
} | |
class SomethingTest { | |
def isSomething[T : Manifest : NotNothing](): Boolean = manifest[T].runtimeClass != classOf[Nothing] | |
} | |
class NotUnitTest { | |
def isNotUnit[T : Manifest : NotUnit](): Boolean = manifest[T].runtimeClass != classOf[Unit] | |
} | |
class NotUnitNotNothingTest { | |
def isNotUnitOrNothing[T : Manifest : NotUnit : NotNothing](): Boolean = | |
manifest[T].runtimeClass != classOf[Unit] && manifest[T].runtimeClass != classOf[Nothing] | |
} | |
class NotTypeSpec extends FlatSpec with Matchers with NotTypeImplicits { | |
"Not type" should "allow" in { | |
val holder = new NotTypeTest | |
assert(holder.isNotType[String]()) | |
} | |
it should "enforce" in { | |
"new NotTypeTest().isNotType[Long]()" shouldNot compile | |
} | |
"Not nothing" should "allow" in { | |
val holder = new SomethingTest | |
assert(holder.isSomething[String]()) | |
} | |
it should "enforce" in { | |
"new SomethingTest().isSomething()" shouldNot compile | |
} | |
"Not unit" should "allow" in { | |
val holder = new NotUnitTest | |
assert(holder.isNotUnit[String]()) | |
} | |
it should "enforce" in { | |
"new NotUnitTest().isNotUnit[Unit]()" shouldNot compile | |
} | |
"Not unit or nothing" should "allow" in { | |
val holder = new NotUnitNotNothingTest | |
assert(holder.isNotUnitOrNothing[String]()) | |
} | |
it should "enforce for unit" in { | |
"new NotUnitOrNothingTest().isNotUnitOrNothing[Unit]()" shouldNot compile | |
} | |
it should "enforce for nothing" in { | |
"new NotUnitOrNothingTest().isNotUnitOrNothing()" shouldNot compile | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package object types { | |
type Id[+A] = A | |
type Not[T] = { | |
type Evidence[U] = U NotTypeOf T | |
} | |
type NotNothing[T] = Not[Nothing]#Evidence[T] | |
type NotUnit[T] = Not[Unit]#Evidence[T] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How does this work? You're leveraging typeclass ambiguities to break the compiler. Walk through this
The first line is an allowed implicit if A and B are unrelated, we return an instance of
NotTypeOf[A, B]
(which in shorthand is writing with infix ofA NotTypeOf B
).If B is a superclass of A the next two implicits match. When multiple implicits match the compiler throws an ambiguious implicits error and fails. If someone is asking for an instance of
NotTypeOf[A, B]
these two lines are hit and they become an error.Now, how to specifically say "I dont want a
Unit
type"? All I have to do is ask for aNotTypeOf[Unit, Unit]
which will cause the compiler to fail. Look at this:I want some
T
where there is a typeclass that the compiler can generate that gives meNotTypeOf[T, Unit]
. What happens if T is a Long?Long is not a subtype of Unit, so the first implicit gets hit:
And the typeclass (
n
) is null. Since we dont' really care what the value ofn
is this code compiles!Now lets say that we do:
We're asking for an implicit
n
of typeNotTypeOf[Unit, Unit]
. ButUnit
IS a subtype ofUnit
! Any type is also its subtype and supertype. For examplecompiles fine
Ok, back to it. We're asking for
NotTypeOf[Unit, Unit]
so the second AND third implicits are invoked! That causes a compiler error.A few last caveats for the non-scala, the syntax of
Really desugars into
Which desugars into
Which desguars into
Why is all this shit cool? Cause you can now require generics to be passed in for methods that otherwise take no arguments. You can do stuff like:
For example, imagine a redis client that has the method:
In java you'd have to do
But even if you dont need the class object, you can still force type safety:
Imagine
In java you could call a method that requires a generic but not pass in the generic type, and java will infer
Object
as the type. So everything typechecks because it has no types. In Scala however, we can force typechecking and limit certain types on the generics.--
Also, hi patrick!