0

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] = ???
Balázs Édes
  • 13,452
  • 6
  • 54
  • 89
  • 1
    Your example doesn't use a context bound anywhere. It uses implicits. – Yuval Itzchakov Sep 23 '16 at 13:08
  • You are right, updated. Also feel free to update the title if you know the name of what I'm trying to do. – Balázs Édes Sep 23 '16 at 13:12
  • Do you mean an existential type? like `val buildables: Seq[HasBuild[_]] = Seq(IntBuilder, SomeStringBuilder)` – Yuval Itzchakov Sep 23 '16 at 13:18
  • Nope. I'd like to have a `Seq` of _anything_ for which there is an implicit `HasBuild` in scope. – Balázs Édes Sep 23 '16 at 13:21
  • So: `def foo[T: HasBuild](input: Seq[T])`? – Yuval Itzchakov Sep 23 '16 at 13:22
  • 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. – Balázs Édes Sep 23 '16 at 13:23
  • What you are suggesting wouldn't work for like `foo(Seq(1, "a"))` if both `Int` and `String` had an appropriate `HasBuild` in scope. – Balázs Édes Sep 23 '16 at 13:27
  • There is no such thing as "a type with a context bound", context bound is simply a syntactic sugar for a method or class definition, so it can't be magically extracted from a method if I understand what you are trying to do. – Victor Moroz Sep 23 '16 at 13:28
  • If you want `foo(Seq(1, "a"))`, you'd need an implicit `HasBuilder[Any]`. Otherwise, what type would you expect the compiler to infer? If you want the compiler to automagically extract the type information from the underlying concrete type, I'm not sure that's something you can easily do. – Yuval Itzchakov Sep 23 '16 at 13:29
  • I think you got it @VictorMoroz. Do you know about any other idiomatic way of "attaching" functionality to unrelated types, then taking a Sequence of types that have this functionality? – Balázs Édes Sep 23 '16 at 13:31
  • @YuvalItzchakov I don't want an implicit object for any. Basically that way I could take a seq of anything and do the type checking runtime which is not what I want. – Balázs Édes Sep 23 '16 at 13:32
  • @bali182 That's exactly what I thought, I know that's something you wouldn't want to do. But I don't see a way of attaching implicit evidences of types the way you want it. – Yuval Itzchakov Sep 23 '16 at 13:33
  • `Seq(1, "a")` has a type `Seq[Any]`, if you don't want `Any` simply create a base class and derive all your classes from it. Obviously `Int` and `String` won't work that way. – Victor Moroz Sep 23 '16 at 13:36
  • The issue is I can't (and don't want to) derive them from a base class. Thanks guys, I have to come up with something better. – Balázs Édes Sep 23 '16 at 13:43

2 Answers2

1

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(_))
Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
0

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
}
flavian
  • 28,161
  • 11
  • 65
  • 105
  • Thanks, but I'd like to do it dependency free. The solution @Alexey suggested wraps everything in a case class but it happens automatically through implicits so it's ok for me. – Balázs Édes Sep 23 '16 at 15:30
  • @bali182 I would agree, it's cleaner; I didn't know you could do that – flavian Sep 23 '16 at 20:39