Balázs Édes
Balázs Édes

Reputation: 13816

Scala context bounds

Using context bounds in scala you can do stuff like

trait HasBuild[T] {
  def build(buildable: T): Something
}

object Builders {
  implict object IntBuilder extends HasBuild[Int] {
    override def build(i: Int) = ??? // Construct a Something however appropriate
  }
}

import Builders._
def foo[T: HasBuild](input: T): Something = implicitly[HasBuild[T]].build(1)
val somethingFormInt = foo(1)

Or simply

val somethingFromInt = implicitly[HasBuild[Int]].build(1)

How could I express the type of a Seq of any elements that have an appropriate implicit HasBuild object in scope? Is this possible without too much magic and external libraries?

Seq[WhatTypeGoesHere] - I should be able to find the appropriate HasBuild for each element

This obviously doesn't compile:

val buildables: Seq[_: HasBuild] = ???

Upvotes: 0

Views: 333

Answers (2)

Alexey Romanov
Alexey Romanov

Reputation: 170909

Basically I'd like to be able to handle unrelated types in a common way (e.g.: build), without the user wrapping them in some kind of adapter manually - and enforce by the compiler, that the types actually can be handled. Not sure if the purpose is clear.

Something you can do:

case class HasHasBuild[A](value: A)(implicit val ev: HasBuild[A])
object HasHasBuild {
  implicit def removeEvidence[A](x: HasHasBuild[A]): A = x.value
  implicit def addEvidence[A: HasBuild](x: A): HasHasBuild[A] = HasHasBuild(x)
}

and now (assuming you add a HasBuild[String] for demonstration):

val buildables: Seq[HasHasBuild[_]] = Seq(1, "a")

compiles, but

val buildables1: Seq[HasHasBuild[_]] = Seq(1, "a", 1.0)

doesn't. You can use methods with implicit HasBuild parameters when you have only a HasHasBuild:

def foo1[A](x: HasHasBuild[A]) = {
  import x.ev // now you have an implicit HasBuild[A] in scope
  foo(x.value)
}

val somethings: Seq[Something] = buildables.map(foo1(_))

Upvotes: 1

flavian
flavian

Reputation: 28511

First things first, contrary to some of the comments, you are relying on context bounds. Requesting an implicit type class instance for a T is what you call a "context bound".

What you want is achievable, but not trivial and certainly not without other libraries.

import shapeless.ops.hlist.ToList
import shapeless._
import shapeless.poly_

object builder extends Poly1 {
  implicit def caseGeneric[T : HasBuilder] = {
    at[T](obj => implicitly[HasBuilder[T]].build(obj))
  }
}

class Builder[L <: HList](mappings: L) {
  def build[HL <: HList]()(
    implicit fn: Mapper.Aux[builder.type, L, HL],
    lister: ToList[Something]
  ) = lister(mappings map fn)

  def and[T : HasBuilder](el: T) = new Builder[T :: L](el :: mappings)
}


object Builder {
  def apply[T : HasBuilder](el: T) = new Builder(el :: HNil)
}

Now you might be able to do stuff like:

Builder(5).and("string").build()

This will call out the build methods from all the individual implcit type class instances and give you a list of the results, where every result has type Something. It relies on the fact that all the build methods have a lower upper bound of Something, e.g as per your example:

trait HasBuild[T] {
  def build(buildable: T): Something
}

Upvotes: 0

Related Questions