The java Predicate interface provides an or method that is used to combine multiple predicates. Example:
Predicate<Integer> p1;
Predicate<Integer> p2;
Predicate<Integer> p2 = p1.or(p2)
Since scala does not have the Predicate interface but uses Function1[A, Boolean] for this purpose what would be the best way to write this? Preferably without the use of external libraries.
I try to avoid the following:
val p1: (A => Boolean)
val p2: (A => Boolean)
val p3: (A => Boolean) = (a => p1(a) || p2(a))
You could provide the Predicate functionality in an implicit value class as
implicit class Predicate[A](val p1: A => Boolean) extends AnyVal {
def or[B >: A](p2: B => Boolean) = (a: A) => p1(a) || p2(a)
}
and then do
val gtOne = (x: Int) => x > 1
val ltMinusOne = (x: Int) => x < -1
val absGtOne = gtOne or ltMinusOne
absGtOne(-2) // true
absGtOne(0) // false
absGtOne(2) // true
You could provide an implicit conversion from A ⇒ Boolean to Predicate which supports the operations you want. Something like this:
implicit class Predicate[A](val p: A ⇒ Boolean) extends AnyVal {
type Predicate[A] = A ⇒ Boolean
def unary_! = (a: A) ⇒ !p(a)
def ==[B >: A](x: Predicate[B]) = (a: A) ⇒ p(a) == x(a)
def !=[B >: A](x: Predicate[B]) = (a: A) ⇒ p(a) != x(a)
def ||[B >: A](x: Predicate[B]) = (a: A) ⇒ p(a) || x(a)
def &&[B >: A](x: Predicate[B]) = (a: A) ⇒ p(a) && x(a)
def ^[B >: A](x: Predicate[B]) = (a: A) ⇒ p(a) ^ x(a)
def |[B >: A](x: Predicate[B]) = (a: A) ⇒ p(a) | x(a)
def &[B >: A](x: Predicate[B]) = (a: A) ⇒ p(a) & x(a)
}
This implements all the methods available in Boolean with the same semantics, e.g. Predicate.&& will only evaluate the second predicate if the first evaluates to true, whereas Predicate.| will always evaluate both predicates.
It's not part of the standard library, but if you only need simple expressions, you could:
Use the Java version, possibly using extension methods for syntactic sugar like p1 || p2 instead of p1.or(p2).
Roll your own case class Predicate[-A](p: A => Boolean) with a few simple methods like:
def ||[B <: A](other: Predicate[B]): Predicate[B] = ???
My stateful predicate:
/**
* Predicate logic with the state to print the function in natural language
*/
object StatefulPredicate {
class Predicate[E, T](predicate: E => T => Boolean, e: E, chain: Option[(Operator, Predicate[E, T])] = None) {
def test(t: T): Boolean = chain.map(c => c._1.execute(t, c._2.test(t), predicate(e))).getOrElse(predicate(e)(t))
def and(e: E): Predicate[E, T] = new Predicate[E, T](this.predicate, e, Some(AND, this))
def or(e: E): Predicate[E, T] = new Predicate[E, T](this.predicate, e, Some(OR, this))
override def toString: String = chain.map(c => c._2.toString + " " + c._1.toString + " " + e.toString).getOrElse(e.toString)
}
sealed trait Operator {
def execute[T](t: T, op1: Boolean, op2: T => Boolean): Boolean
}
case object AND extends Operator {
override def execute[T](t: T, op1: Boolean, op2: T => Boolean): Boolean = op1 && op2(t)
override def toString: String = "and"
}
case object OR extends Operator {
override def execute[T](t: T, op1: Boolean, op2: T => Boolean): Boolean = op1 || op2(t)
override def toString: String = "or"
}
def main(args: Array[String]): Unit = {
val p1: Predicate[Int, Seq[Int]] = new Predicate(e => s => s.contains(e), 10)
val p2: Predicate[Int, Seq[Int]] = p1.or(25)
val p3: Predicate[Int, Seq[Int]] = p2.and(25)
val p4: Predicate[Int, Seq[Int]] = p3.and(40)
print(p4.test(Seq[Int](10, 20, 30, 40)))
}
}
Related
I have some overloaded methods that take in multiple types and return the same type:
def foo(x: Int): Foo = ...
def foo(x: String): Foo = ...
def foo(x: Boolean): Foo = ...
def foo(x: Long): Foo = ...
Now I want to define a single way to call the method, something like:
def bar(x: Int | String | Boolean | Long) = foo(x) // how to do this?
I can do it the "naive" way which I don't like very much:
def bar(x: Any) = x match {
case i:Int => foo(i)
case s:String => foo(s)
case b:Boolean => foo(b)
case l:Long => foo(l)
case _ => throw new Exception("Unsupported type")
}
Is there a better way, perhaps using Scalaz or some other library?
Try type class
trait FooDoer[T] {
def foo(x: T): Foo
}
object FooDoer {
implicit val int: FooDoer[Int] = (x: Int) => foo(x)
implicit val string: FooDoer[String] = (x: String) => foo(x)
implicit val boolean: FooDoer[Boolean] = (x: Boolean) => foo(x)
implicit val long: FooDoer[Long] = (x: Long) => foo(x)
}
def bar[T](x: T)(implicit fooDoer: FooDoer[T]): Foo = fooDoer.foo(x)
bar(1)
bar("a")
bar(true)
bar(1L)
// bar(1.0) // doesn't compile
Also sometimes the following can help
def bar[T](x: T)(implicit ev: (T =:= Int) | (T =:= String) | (T =:= Boolean) | (T =:= Long)) = ???
trait |[A, B]
trait LowPriority_| {
implicit def a[A, B](implicit a: A): A | B = null
}
object | extends LowPriority_| {
implicit def b[A, B](implicit b: B): A | B = null
}
How to define "type disjunction" (union types)?
A typeclass might work like this:
trait CanFoo[T] {
def foo(t: T): Foo
}
object CanFoo {
implicit object intFoo extends CanFoo[Int] {
def foo(i: Int) = Foo(i)
}
implicit object stringFoo extends CanFoo[String] {
def foo(s: String) = Foo(s)
}
implicit object boolFoo extends CanFoo[Boolean] {
def foo(i: Boolean) = Foo(i)
}
implicit object longFoo extends CanFoo[Long] {
def foo(i: Long) = Foo(i)
}
}
def bar[T](x: T)(implicit ev: CanFoo[T]) =
ev.foo(x)
bar(0)
bar("hello")
bar(true)
bar(0.toLong)
I've been doing an exercise to try to implement a basic Calculator with Free Monad.
As I understand the intention of the Free Monad and what I wanted to achieve is:
write my program (math expression) once run it with different interpreters.
Now i am not sure that I did the 100% idiomatic implementation at least because:
My program kinda needs to be parametrized on the generic type A which should match the interpreter context.
def program[A] = for {
two <- lit[A](2)
four <- lit[A](4)
sum <- add(two, four)
} yield sum
program[Int].foldMap(eval) shouldBe 6
program[String].foldMap(print) shouldBe "(2 + 4)"
import cats.instances.option._
program[Option[Int]].foldMap(evalOpt) shouldBe Option(6)
The ADT/algebra and 'smart constructors'
trait Expression2[A] extends Product with Serializable
case class Lit[A](a: Int) extends Expression2[A]
case class Add[A](a: A, b: A) extends Expression2[A]
case class Mult[A](a: A, b: A) extends Expression2[A]
type ExprAlg[B] = Free[Expression2, B]
def lit[A](a: Int): ExprAlg[A] = Free.liftF(Lit(a))
def add[A](a: A, b: A): ExprAlg[A] = Free.liftF(Add(a, b))
def mult[A](a: A, b: A): ExprAlg[A] = Free.liftF(Mult(a, b))
The math interpreter:
def eval: Expression2 ~> Id = new (Expression2 ~> Id) {
override def apply[A](fa: Expression2[A]): Id[A] = eval(fa).asInstanceOf[A]
def eval[A](expression2: Expression2[A]): Int = expression2 match {
case Lit(n) => n
case Add(a, b) => a.asInstanceOf[Int] + b.asInstanceOf[Int]
case Mult(a, b) => a.asInstanceOf[Int] * b.asInstanceOf[Int]
}
}
The print interpreter:
def print: Expression2 ~> Id = new (Expression2 ~> Id) {
override def apply[A](fa: Expression2[A]): Id[A] = eval(fa).asInstanceOf[A]
def eval[A](expression2: Expression2[A]): String = expression2 match {
case Lit(n) => n.toString
case Add(a, b) => "(" + a.toString + " + " + b.toString + ")"
case Mult(a, b) => "(" + a.toString + " * " + b.toString + ")"
}
}
The math in Option interpreter:
def evalOpt: Expression2 ~> Option = new (Expression2 ~> Option) {
override def apply[A](fa: Expression2[A]): Option[A] = eval(fa).map{_.asInstanceOf[A]}
def eval[A](expression2: Expression2[A]): Option[Int] = expression2 match {
case Lit(n) => Option(n)
case Add(a, b) => Option(a.asInstanceOf[Int] + b.asInstanceOf[Int])
case Mult(a, b) => Option(a.asInstanceOf[Int] * b.asInstanceOf[Int])
}
}
Related to the Option interpreter, I would have expected that the a and b vars to be option, and in the string interpreter a and b to be strings because of my the ADT result type is A: Expression2[A].
I also tried instead of Lit[A](a: Int), to have Lit[A](a: A) but then it breaks down: i cannot pass different interpreters for the same expression when A is fixed to an Int in my program and I expect not to have to rewrite my program for different interpreters.
So a couple things. Generally you reaaaally want to avoid asInstanceOf because right now you can construct an Expression2 with any type and then it'd just crash on evaluating because it's not actually an Int. There's a couple ways to mitigate this. You can either just fix the type of the contained numeric type in your Expression2
import scalaz._
import Scalaz._
trait Expression2[A] extends Product with Serializable
case class Lit[A](a: Int) extends Expression2[Int]
case class Add[A](a: Int, b: Int) extends Expression2[Int]
case class Mult[A](a: Int, b: Int) extends Expression2[Int]
type ExprAlg[A] = Free[Expression2, A]
def lit(a: Int): ExprAlg[Int] = Free.liftF(Lit(a))
def add(a: Int, b: Int): ExprAlg[Int] = Free.liftF(Add(a, b))
def mult(a: Int, b: Int): ExprAlg[Int] = Free.liftF(Mult(a, b))
val eval: Expression2 ~> Id = new (Expression2 ~> Id) {
override def apply[A](fa: Expression2[A]): Id[A] = eval(fa)
def eval[A](expression2: Expression2[A]): A = expression2 match {
case Lit(n) => n
case Add(a, b) => a+b
case Mult(a, b) => a*b
}
}
Or you can associate the capability with the operations like this. basically you can think of the cases in your ADT like Add like this: The parameters of the case class are like function parameters and the type you put into the Extends is the result type.
import scalaz._
import Scalaz._
import spire.algebra._
import spire.implicits._
trait Expression2[A] extends Product with Serializable
case class Lit[A](a: A) extends Expression2[A]
case class Add[A](a: A, b: A)(implicit val ev:Semiring[A]) extends Expression2[A]
case class Mult[A](a: A, b: A)(implicit val ev:Semiring[A]) extends Expression2[A]
type ExprAlg[A] = Free[Expression2, A]
def lit[A](a: A): ExprAlg[A] = Free.liftF(Lit(a))
def add[A](a: A, b: A)(implicit ev:Semiring[A]): ExprAlg[A] = Free.liftF(Add(a, b))
def mult[A](a: A, b: A)(implicit ev:Semiring[A]): ExprAlg[A] = Free.liftF(Mult(a, b))
val eval: Expression2 ~> Id = new (Expression2 ~> Id) {
override def apply[A](fa: Expression2[A]): Id[A] = eval(fa)
def eval[A](expression2: Expression2[A]): Id[A] = expression2 match {
case Lit(n) => n
case x:Add[A] => x.ev.plus(x.a,x.b)
case x:Mult[A] => x.ev.times(x.a,x.b)
}
}
def program[A: Semiring](a:A,b:A) = for {
two <- lit(a)
four <- lit(b)
sum <- add(two, four)
} yield sum
println(program[Int](2,4).foldMap(eval) )
Now as for your Option case I am not quite sure why you want to interpret into an Option here. if you can do F ~> Id for some F, F ~> Option really is just Some applied to the first natural transformation.
I would like to "compose" two monoids using cats. If there exists a defined Monoid[(A, A) => Int], then I would like to be able to create a Monoid[Preference[A]] using the combine and empty methods of the Monoid[(A, A) => Int]. I am using the term "composing" loosely here because I am not sure that the transform I want to do is accurately called composition.
Here is my current attempt...
import cats._
import cats.implicits._
trait Preference[A] extends Order[A]
object Preference {
def from[A](f: (A, A) => Int): Preference[A] = {
new Preference[A] {
def compare(a1: A, a2: A): Int = {
f(a1, a2)
}
}
}
def monoid[A](implicit ev: Monoid[(A, A) => Int]): Monoid[Preference[A]] = {
new Monoid[Preference[A]] {
def combine(p1: Preference[A], p2: Preference[A]): Preference[A] = {
new Preference[A] {
def compare(a1: A, a2:A): Int = {
ev.combine(p1.compare, p2.compare)(a1, a2)
}
}
}
def empty: Preference[A] = {
from(ev.empty)
}
}
}
}
...this compiles but I would like to know if there is a more idiomatic solution available using cats.
Seems like it should be possible to somehow compose the Monoid[(A,A) => Int] with the from combinator that takes a f:(A, A) => Int and returns a Preference[A] to create a Monoid[Preference[A]] but I can not figure out how to do it.
I have seen this SO post which discusses composing monoids using a product combinator which is not what I want.
I'm not aware of anything built-in into cats directly.
It seems that you have an isomorphism between Preference[A] and (A, A) => Int, and you simply want to transfer the monoid-structure from (A, A) => Int to Preference[A]. This can be expressed generically for arbitrary types A and B:
def fromIsomorphicMonoid[A, B](
forward: A => B,
inverse: B => A
)(implicit aMon: Monoid[A]): Monoid[B] = new Monoid[B] {
def combine(b1: B, b2: B): B =
forward(aMon.combine(inverse(b1), inverse(b2)))
def empty: B = forward(aMon.empty)
}
With this helper method, your monoid in Preference becomes just:
def monoid[A](implicit ev: Monoid[(A, A) => Int]): Monoid[Preference[A]] =
fromIsomorphicMonoid(
from,
(p: Preference[A]) => (x:A, y:A) => p.compare(x, y)
)
Full compilable example (without any dependencies):
trait Monoid[X] {
def empty: X
def combine(x: X, y: X): X
}
trait Order[A] {
def compare(a1: A, a2: A): Int
}
def fromIsomorphicMonoid[A, B](
forward: A => B,
inverse: B => A
)(implicit aMon: Monoid[A]): Monoid[B] = new Monoid[B] {
def combine(b1: B, b2: B): B =
forward(aMon.combine(inverse(b1), inverse(b2)))
def empty: B = forward(aMon.empty)
}
trait Preference[A] extends Order[A]
object Preference {
def from[A](f: (A, A) => Int): Preference[A] = {
new Preference[A] {
def compare(a1: A, a2: A): Int = {
f(a1, a2)
}
}
}
def monoid[A](implicit ev: Monoid[(A, A) => Int])
: Monoid[Preference[A]] = fromIsomorphicMonoid(
from,
(p: Preference[A]) => (x:A, y:A) => p.compare(x, y)
)
}
Edit:
Last revision was deemed unhelpful as it did not contain necessary information that help narrow down my issue. hence the need to also include the AST.
Below is a library in its entirety that allows parsing and writing of play-json's json based on user defined schema; Similar to what Scala's slick offers for database columns to some extent:
import scala.language.higherKinds
import play.api.libs.functional.syntax._
import play.api.libs.json._
import scala.language.{higherKinds, implicitConversions}
type PathNodes = List[PathNode]
sealed trait Field[A] {
def pathNodes: PathNodes
def jsPath: JsPath = JsPath(pathNodes)
def relativePath: JsPath = JsPath(List(pathNodes.last))
def format: Format[A]
def nestedFormatter(path: JsPath): OFormat[A]
def nestedFormat: OFormat[A] = nestedFormatter(relativePath)
}
case class PlainField[A: Format](prefix: PathNodes) extends Field[A] {
override def pathNodes: PathNodes = prefix
def format: Format[A] = implicitly[Format[A]]
override def nestedFormatter(path: JsPath): OFormat[A] = path.format(format)
}
abstract class JsonSchema[T](val _prefix: PathNodes) extends Field[T] with SchemaExtensionMethods {
override def pathNodes: PathNodes = _prefix
def format: OFormat[T]
protected def plain[A: Format](name: String): PlainField[A] = PlainField[A](_prefix :+ KeyPathNode(name))
protected def nested[N](name: String, factory: PathNodes => N): N = factory(_prefix :+ KeyPathNode(name))
protected def nested[B, G <: JsonSchema[B]](name: String)(implicit sm: HasJsonSchema[B, G]): G = sm.apply(_prefix :+ KeyPathNode(name))
override def nestedFormatter(path: JsPath): OFormat[T] = path.format(format)
}
case class Optional[F, A](field: F)(implicit ev: F <:< Field[A]) extends Field[Option[A]] {
override def pathNodes: PathNodes = field.pathNodes
override def format: Format[Option[A]] = {
implicit val writes: Writes[Option[A]] = JsPath.writeNullable(field.format)
implicit val reads: Reads[Option[A]] = JsPath.readNullable(field.format)
implicitly[Format[Option[A]]]
}
def map[G, B](f: F => G)(implicit ev: G <:< Field[B]): Optional[G, B] = new Optional[G, B](f(field))
def flatMap[G <: Field[B], B](f: F => Optional[G, B]): Optional[G, B] = f(field)
override def nestedFormatter(path: JsPath): OFormat[Option[A]] = path.formatNullable(field.format)
}
case class Collection[F, A](field: F)(implicit ev: F <:< Field[A], repath: Repath[F]) extends Field[Seq[A]] {
override def pathNodes: PathNodes = field.pathNodes
override def format: Format[Seq[A]] = {
implicit val writes: Writes[Seq[A]] = Writes.seq(field.format)
implicit val reads: Reads[Seq[A]] = Reads.seq(field.format)
implicitly[Format[Seq[A]]]
}
def apply(idx: Int): F = implicitly[Repath[F]].apply(field, IdxPathNode(idx))
override def nestedFormatter(path: JsPath): OFormat[Seq[A]] = path.format(format)
}
class FormatExtensionMethods[T](val arg: T) {
def <>[A, B, Fun](apply: Fun, unapply: B => Option[A])(implicit jss: JsonShape[A, B, T, Fun]): OFormat[B] = jss.format(arg, apply, unapply andThen (_.get))
}
class FieldExtensionMethods[F](val field: F) {
def optional[A](implicit ev: F <:< Field[A]): Optional[F, A] = new Optional[F, A](field)
def sequence[A](implicit ev: F <:< Field[A], repath: Repath[F]): Collection[F, A] = new Collection[F, A](field)
}
trait SchemaExtensionMethods {
implicit def formatExtensionMethods[M](t: M): FormatExtensionMethods[M] = new FormatExtensionMethods[M](t)
implicit def fieldExtensionMethods[M, A](t: M): FieldExtensionMethods[M] = new FieldExtensionMethods[M](t)
}
trait Repath[F] {
def apply(f: F, node: PathNode): F
}
object Repath {
implicit def plain[T]: Repath[PlainField[T]] = new Repath[PlainField[T]] {
override def apply(t: PlainField[T], node: PathNode): PlainField[T] =
PlainField[T](t.pathNodes :+ node)(t.format)
}
implicit def schema[S <: JsonSchema[_]](implicit sm: HasJsonSchema[_, S]): Repath[S] = new Repath[S] {
override def apply(t: S, node: PathNode): S =
sm.apply(t.pathNodes :+ node)
}
implicit def option[F <: Field[T] : Repath, T]: Repath[Optional[F, T]] = new Repath[Optional[F, T]] {
override def apply(t: Optional[F, T], node: PathNode): Optional[F, T] =
new Optional[F, T](implicitly[Repath[F]].apply(t.field, node))
}
implicit def sequence[F <: Field[T] : Repath, T]: Repath[Collection[F, T]] = new Repath[Collection[F, T]] {
override def apply(t: Collection[F, T], node: PathNode): Collection[F, T] =
new Collection[F, T](implicitly[Repath[F]].apply(t.field, node))
}
}
trait JsonShape[A, B, -T, Func] {
def format(t: T, apply: Func, unapply: B => A): OFormat[B]
}
object JsonShape {
type F[T] = Field[T]
implicit def cc1[A, B]: JsonShape[A, B, F[A], (A) => B] = (t: F[A], apply: (A) => B, unapply: B => A) => {
val name = t.pathNodes.last.asInstanceOf[KeyPathNode].key
OFormat[B](
Reads[B](jsv => (jsv \ name).validate[A](t.format).map(apply)),
OWrites[B](b => JsObject(Map(name -> Json.toJson(unapply(b))(t.format))))
)
}
implicit def cc2[T1, T2, B]: JsonShape[(T1, T2), B, (F[T1], F[T2]), (T1, T2) => B] = (t: (F[T1], F[T2]), apply: (T1, T2) => B, unapply: B => (T1, T2)) => {
(
t._1.nestedFormat and
t._2.nestedFormat
) (apply, unapply)
}
implicit def cc3[T1, T2, T3, B]: JsonShape[(T1, T2, T3), B, (F[T1], F[T2], F[T3]), (T1, T2, T3) => B] = (t: (F[T1], F[T2], F[T3]), apply: (T1, T2, T3) => B, unapply: B => (T1, T2, T3)) => {
(
t._1.nestedFormat and
t._2.nestedFormat and
t._3.nestedFormat
) (apply, unapply)
}
//this goes up to 22
}
abstract class HasJsonSchema[T, +S <: JsonSchema[T]](val apply: PathNodes => S) extends OFormat[T] {
val root: S = apply(Nil)
def format: OFormat[T] = root.format
def writes(o: T): JsObject = root.format.writes(o)
def reads(json: JsValue): JsResult[T] = root.format.reads(json)
}
Now let's write a small piece of client code that reproduce the issue:
case class MessageSchema(prefix: PathNodes) extends JsonSchema[Message](prefix) {
def underlying = plain[String]("underlying")
//def underlying = plain[String]("underlying").optional if I wanted the field to be Option[String]
//def underlying = plain[String]("underlying").sequence if I wanted the field to be Seq[String]
override def format = underlying <> (Message.apply _, Message.unapply)
}
case class Message(underlying: String)
object Message {
implicit object sm extends HasJsonSchema[Message, MessageSchema](MessageSchema.apply)
}
case class LanguageTaggedSchema[T, S <: JsonSchema[T]](prefix: PathNodes)(implicit evT: HasJsonSchema[T, S]) extends JsonSchema[LanguageTagged[T]](prefix) {
def lang = plain[String]("lang")
def data: S = nested("data")(evT)
def format = (lang, data) <> (LanguageTagged.apply[T] _, LanguageTagged.unapply[T])
}
case class LanguageTagged[T](lang: String, data: T)
object LanguageTagged {
implicit def schemaMapper[T, S <: JsonSchema[T]](implicit ev: HasJsonSchema[T, S]): HasJsonSchema[LanguageTagged[T], LanguageTaggedSchema[T, S]] =
new HasJsonSchema[LanguageTagged[T], LanguageTaggedSchema[T, S]](LanguageTaggedSchema.apply[T, S]) {}
}
def toJson[T, S <: JsonSchema[T]](a: T)(implicit ev: HasJsonSchema[T, S]): JsValue = Json.toJson(a)(ev.format)
toJson(Message("hi")) //Ok!
toJson(LanguageTagged("en", Message("hi"))) //Ok!
//or simply write
Json.toJson(LanguageTagged("en", Message("hi")))
//and if i wanted to traverse a json path i would do:
val schema = implicitly[HasJsonSchema[LanguageTagged[Message],LanguageTaggedSchema[Message,MessageSchema]]].root
schema.data.underlying.jsPath
//prints: res2: play.api.libs.json.JsPath = /data/underlying
//Now to where the problem starts:
def getSchema[T, S <: JsonSchema[T]](a: T)(implicit ev: HasJsonSchema[T, S]): S = ev.root
getSchema(Message("hi")) //Ok!
getSchema(LanguageTagged("en", Message("hi"))) //Not Ok but why?
//Error:(211, 11) could not find implicit value for
//parameter ev: A$A6.this.HasJsonSchema[A$A6.this.LanguageTagged[A$A6.this.Message],S]
//getSchema(LanguageTagged("en", Message("hi")));//
//^
I have a huge suspicion that the compiler runs into issues because of the bounded type of S inHasJsonSchema[T, S <: JsonSchema[T]] when infering the implicit type S. and so far only in that specific situation as shown on the last line of all the code. as a dubugging attempt I created a similar situation and realized that if the type S was not bounded I wouldn't have this issue. Any sort of solution that refactors the code such that it doesn't depend on bounded types or one that simply solves the implicit resolution is appreciated
What You're trying to achieve cannot be done with subtyping. You should use type-classes instead, a more in-depth explanation:
http://danielwestheide.com/blog/2013/02/06/the-neophytes-guide-to-scala-part-12-type-classes.html
I've got an interface,
trait Heap[E, H <: Heap[E, H]] {
implicit def ord: Ordering[E]
def empty: H
def isEmpty: Boolean
def insert(x: E): H
def merge(b: H): H
def findMin: E
def deleteMin: H
def toList: List[E] =
if (isEmpty) Nil else findMin :: deleteMin.toList
}
It's generic, as we need H to define "a Heap of the same type," as def merge(b: H): H is normally defined in terms of merging heaps of the same internal structure.
That works reasonably well for the "normal" heaps, i.e. heaps that manage their internal structures themselves:
class LazyPairingHeap[E](val h: Repr[E] = Empty)
(implicit val ord: Ordering[E])
extends Heap[E, LazyPairingHeap[E]] {
import okasaki.heaps.LazyPairingHeap._
override def empty = new LazyPairingHeap[E](Empty)
override def isEmpty: Boolean = h == Empty
// etc.
and even for some of the heaps build on top of other heaps:
class SizedHeap[E, H <: Heap[E, H]](val s: Int, val h: H)
extends Heap[E, SizedHeap[E, H]] {
override implicit def ord: Ordering[E] = h.ord
override def empty = new SizedHeap[E, H](0, h.empty)
override def isEmpty = s == 0
override def insert(e: E) = new SizedHeap[E, H](s + 1, h.insert(e))
override def merge(o: SizedHeap[E, H]) = new SizedHeap[E, H](s + o.s, h.merge(o.h))
override def findMin: E = h.findMin
override def deleteMin = new SizedHeap[E, H](s - 1, h.deleteMin)
}
The problem appears when instead of merely wrapping the original heap we need to weave it into our representation:
object BootstrappedHeap {
sealed trait BSHeap[+A, +H[_]]
object Empty extends BSHeap[Nothing, Nothing] {
override def toString = "Empty"
}
case class H[A, BH[_]](x: A, bsh: BH[BSHeap[A, BH]]) extends BSHeap[A, BH] {
override def toString = s"H($x, $bsh)"
}
}
abstract class BootstrappedHeap[E,
BaseHeap[X] <: Heap[X, BaseHeap[X]],
This <: BootstrappedHeap[E, BaseHeap, This]]
(val h: BSHeap[E, BaseHeap] = Empty)
(implicit ord: Ordering[E])
extends Heap[E, This] {
implicit val hord: Ordering[BSHeap[E, BaseHeap]] =
Ordering.by {
case Empty => None
case H(x, _) => Some(x)
}
def baseHeap: BaseHeap[BSHeap[E, BaseHeap]]
def create(h: BSHeap[E, BaseHeap]): This
override def empty = create(Empty)
override def isEmpty: Boolean = h == Empty
override def insert(x: E) = create(merge(H(x, baseHeap.empty), h))
override def merge(o: This) = create(merge(h, o.h))
private def merge(a: BSHeap[E, BaseHeap], b: BSHeap[E, BaseHeap]): BSHeap[E, BaseHeap] = (a, b) match {
case (Empty, _) => b
case (_, Empty) => a
case (h1#H(x, p1), h2#H(y, p2)) =>
if (ord.lteq(x, y)) H(x, p1.insert(h2))
else H(y, p2.insert(h1))
}
// etc
Now the question is, is there a better way than this lovely type annotation?
[E,
BaseHeap[X] <: Heap[X, BaseHeap[X]],
This <: BootstrappedHeap[E, BaseHeap, This]]
X seems to be unnecessary though I found no way to get rid of it;
a reference to This seems to be a bit over the edge;
finally, BaseHeap has really nothing to do in the public interface: I want it to be an implementation detail, can I hide it?