Skip to content

Instantly share code, notes, and snippets.

@mbloms
Last active April 27, 2020 15:24
Show Gist options
  • Save mbloms/609080b5ac91f03e74284d51a43aae4c to your computer and use it in GitHub Desktop.
Save mbloms/609080b5ac91f03e74284d51a43aae4c to your computer and use it in GitHub Desktop.
package witness
import scala.language.implicitConversions
import scala.reflect._
import scala.collection.immutable._
/**
* Base trait for a Captured instance of type T
* TODO: Naming
* @tparam T
*/
trait Witness[+T] extends Any
//TODO: trait Exorcist[+F[_] <: Witness[_],T] breaks implicit resolution
/**
* Type class for lifting types into a Captured
* @tparam F
* @tparam T
*/
trait Exorcist[+F[_] <: Witness[_],T] {
def evictDemons(x: T): F[T]
}
type Lifter[F <: Witness] = [T] =>> Exorcist[F,T]
/**
* Fresh[T] witnesses that the instance of T in this wrapper is "fresh".
*
* The contain instance must either be guaranteed to be immutable (Frozen)
* or a new instance that is created every time "extract" is called.
*
* @tparam T
*/
sealed trait Fresh[+T] extends Witness[T] {
self =>
def extract: T
}
object Fresh {
def fresh[T: ClassTag]: Fresh[T] =
new Fresh[T] {
override def extract: T =
classTag[T].runtimeClass.newInstance().asInstanceOf[T]
}
// The closure must only capture Fresh values
def asFresh[T](closure: => T): Fresh[T] =
new Fresh[T] {
override def extract: T = closure
}
import scala.collection.mutable._
implicit def shallowCloner[A: Lifter[Immutable],CC[A] <: Cloneable[CC[A]]]: Exorcist[Fresh,CC[A]] =
new Exorcist[Fresh,CC[A]] {
override def evictDemons(x: CC[A]): Fresh[CC[A]] =
new Fresh[CC[A]] {
private val copy: CC[A] = x.clone()
override def extract: CC[A] = copy.clone()
}
}
implicit def deepCloner[A,CC[A] <: IndexedSeqOps[A,CC,CC[A]]](implicit ev: Exorcist[Fresh,A]): Exorcist[Fresh,CC[A]] =
new Exorcist[Fresh,CC[A]] {
override def evictDemons(x: CC[A]): Fresh[CC[A]] =
new Fresh[CC[A]] {
private val copy: CC[Fresh[A]] = x.map(ev.evictDemons(_))
override def extract: CC[A] = copy.map(_.extract)
}
}
}
// Using `T <: Serializable` breaks
// upper bound [_$1] =>> witness.Witness[?]
sealed class Serialized[+T] private (original: T) extends Fresh[T] {
import java.io._
val serialized: Array[Byte] = {
val stream = new ByteArrayOutputStream()
val out = new ObjectOutputStream(stream)
out.writeObject(original)
out.close()
stream.toByteArray
}
override def extract: T = {
val stream = new ByteArrayInputStream(serialized)
val in = new ObjectInputStream(stream)
in.readObject.asInstanceOf[T]
}
}
object Serialized {
def apply[T <: Serializable](x: T): Serialized[T] = new Serialized[T](x)
def unapply[T](serialized: Serialized[T]): Some[T] = Some(serialized.extract)
implicit def serializer[A <: Serializable]: Exorcist[Serialized,A] =
new Exorcist[Serialized,A] {
override def evictDemons(x: A): Serialized[A] =
new Serialized[A](x)
}
}
/**
* Frozen[T] witnesses that the instance of T in this wrapper is immutable.
* This does not neccecarily mean that all T are immutable.
*
* The contained instance must be "frozen" in the sense that
* it must not be possible to make it mutable without making a new instance.
*
* @tparam T
*/
sealed trait Frozen[+T] extends Fresh[T]
/**
* An instance Immutable[T] witnesses that _all_ instances of exactly T is Immutable.
* Unlike Frozen[T], Immutable[T] is invariant in T:
* Immutable[Nothing] is a subtype of Frozen[Any], but not of Immutable[Any],
* because that would mean all instances of Any is immutable.
* @tparam T
*/
sealed abstract class Immutable[T] extends Frozen[T]
// This is the sickest thing ever
/** Lift[F] is a function T => F[T]*/
type Lift[F[_]] = [T] =>> T => F[T]
object Immutable {
// I mean where did all the boilerplate go??
type Primitive = scala.Double
| scala.Float
| scala.Long
| scala.Int
| scala.Char
| scala.Short
| scala.Byte
| scala.Boolean
| scala.Unit
type SafeBottom = scala.Null
| scala.Nothing
| scala.collection.immutable.Nil.type
| scala.None.type
type SafeContainer[_] = scala.collection.immutable.List[_]
| scala.Option[_]
def apply[T](x: T)(implicit exorcist: Exorcist[Immutable,T]): Immutable[T] = {
exorcist.evictDemons(x)
}
def unapply[T](pure: Immutable[T]): Some[T] = Some(pure.extract)
def unapply[T](x: T)(implicit ev: Exorcist[Immutable,T]): Some[T] = Some(x)
implicit def ImmutabilityBlesser[T](implicit bless: T => Immutable[T]): Exorcist[Immutable,T] =
new Exorcist[Immutable,T] {
override def evictDemons(x: T): Immutable[T] = bless(x)
}
// Actually, there are classes like ArrayOps that extend AnyVal, but are mutable :(
//implicit class ImmutableVal[T <: AnyVal](override val extract: T) extends Immutable[T]
//TODO: Think hard about value classes
implicit class ImmutablePrimitive[T <: Primitive](override val extract: T) extends Immutable[T]
implicit class ImmutableBottom[T <: SafeBottom](override val extract: T) extends Immutable[T]
implicit class ImmutableContainer[F <: SafeContainer, T: Lift[Immutable]](override val extract: F[T]) extends Immutable[F[T]]
}
/**
* A Freezer takes an instance of type T, which might not be immutable,
* and returns an instance of T wrapped in Immutable.
* The returned instance might be a copy to guarantee immutability
* @tparam T
*/
type Freezer[T] = Exorcist[Frozen,T]
object Frozen {
import scala.collection.ArrayOps
import scala.reflect.ClassTag
def apply[T](x: T)(implicit freezer: Freezer[T]): Frozen[T] = freezer.evictDemons(x)
implicit def BlessedFreezer[T](implicit blesser: Exorcist[Immutable,T]): Exorcist[Frozen,T] = blesser
private def asFrozen[T](frozen: T): Frozen[T] = new Frozen[T] {
override def extract: T = frozen
}
//private class FrozenInstance[+T](override val extract: T) extends Immutable[T]
//implicit class ImplicitFreezer[T](implicit convert: T => Frozen[T]) extends Freezer[T] {
// override def evictDemons(x: T): Frozen[T] = convert(x)
//}
implicit def ArrayOpsFreezer[A](implicit freezer: Freezer[A], classTag: ClassTag[A]): Freezer[ArrayOps[A]] =
new Freezer[ArrayOps[A]] {
override def evictDemons(xs: ArrayOps[A]): Frozen[ArrayOps[A]] = {
val copy = xs.toArray
asFrozen(new ArrayOps[A](copy))
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment