1

I'm trying to write a factory to create case classes. I'm creating a command line tool that accepts String representations for the parameters of the case class. Anyway the idea being to limit changes for additions to creating an implicit for the new type, but I can't seem to get past the starting line. Below is some code that attempts to create a case object (so no parameters - the most trivial case), but scalac complains that it "cannot find the implicit value for parameter factory". Gotta be something stoopid, but I've tried everything I can think of. If anyone can point me in the right direction, it would be most appreciated. Thanks.

    object Message {

      case object Person


      sealed trait Repr[T] {
        def value: T
      }


      trait Parm0Factory[R] {
        def instance(): R
      }


      implicit val personFactory = new Parm0Factory[Person.type] {
        def instance() = Person
      }


      case class Parm0[R]() extends Repr[R] {
        override def value(): R = xform

        private def xform(implicit factory: Parm0Factory[R]): R = factory.instance()
      }

   def main(args: Array[String]): Unit = { 
      val t1 = Parm0[Person.type]() 
      println(t1.value) 
   }

    }
bbarrington
  • 115
  • 7

2 Answers2

3

You have an implicit definition for PersonFactory[Person.Type], but what your xform call is looking for and not finding is PersonFactory[R]. It is not the same type.

Dima
  • 39,570
  • 6
  • 44
  • 70
  • Yes. But I left out something important. The call site. (Below). Clearly R is not Person.type, but generics would be kind of useless otherwise (??). Should it not resolve to Person.type based on the call information? Maybe declaring the type is not enough - it needs to have a parameter of that type? def main(args: Array[String]): Unit = { val t1 = Parm0[Person.type]() println(t1.value) } – bbarrington Dec 09 '15 at 22:03
  • Generics are indeed useless in this case, yes. It can't "resolve to "Person.type based on the call information", because the "call information" resolves it to `R`. If you declare `value()` to return `Person.Type`, that, would work, except, then it won't conform to `Repr[R]` definition. Now, if you drop the `R` thing altogether, and declare it it as `class Parm0 extends Repr[Person.type]`, that would compile. Does this make sense? – Dima Dec 09 '15 at 22:12
  • Yeah. But I can't accomplish what I am trying to do in that case. That is, create a 'class of classes' that act as factories for case classes - one 'class' for the various number of parameters. So I guess it's becoming obvious that I'm going about this the wrong way. :-) – bbarrington Dec 09 '15 at 22:23
  • Obviously. The very idea of having a `factory` is to have it do something different for different cases. If you only have one defined, you could as well spell it out explicitly. – Dima Dec 09 '15 at 22:31
  • Uh, yes. The 'factory' part would be the R part - the type of the instance to be produced. Do you have any suggestions regarding how to accomplish that? – bbarrington Dec 09 '15 at 22:46
  • I don't quite see what you are trying to accomplish. I am guessing, you have to either have the (implicit) factory available in scope of the caller, or use some sort of reflection. – Dima Dec 09 '15 at 22:53
0

I did some research and was able to come up with an implementation of my idea that for now is quite satisfying (at least for me). Briefly, I wanted to maintain a String representation of a recursive tree structure that a user could modify in place, then be able to convert that representation into an actual instance. Turns out type classes are what I was looking for. Thought I would go ahead and post this for those out there that are at least as unfamiliar with using type classes as I was. If anyone knows how to remove the necessity of having to provide the type parameter to the invocation of 'build', please let me know. Also, I'd like to have a cleaner implementation of the implicit builder functions that doesn't use the '@unchecked' annotation to eliminate the annoying compiler warnings. :-)

object Messages {

  // Test classes
  case class Person(name: String, age: Int, pet: Dog)

  case class Dog(name: String)

  case class Device(deviceType: DeviceType, on: Boolean)

  sealed trait DeviceType

  case class Android() extends DeviceType

  case class iOS() extends DeviceType

  case class Windows() extends DeviceType



  // Builders...
  trait Builder[A] {
    def build: Node => A
  }

  object Builder {
    def build[A](f: Node => A) = new Builder[A] {
      val build = f
    }

    // Terminals...
    implicit val intBuilder: Builder[Int] = build(node => (node: @unchecked) match {
      case Term(_, value) => value.toInt
    })
    implicit val stringBuilder: Builder[String] = build(node => (node: @unchecked) match {
      case Term(_, value) => value
    })
    implicit val booleanBuilder: Builder[Boolean] = build(node => (node: @unchecked) match {
      case Term(_, value) => value.toBoolean
    })


    // Case classes (composites)
    implicit val dogBuilder: Builder[Dog] = build[Dog](node => (node: @unchecked) match {
      case Tree(_, children) =>
        Dog(children(0).build[String])
    })

    implicit val personBuilder: Builder[Person] = build[Person](node => (node: @unchecked) match {
      case Tree(_, children) =>
        Person(children(0).build[String], children(1).build[Int], children(2).build[Dog])
    })

    implicit val deviceTypeBuilder: Builder[DeviceType] = build[DeviceType](node => (node: @unchecked) match {
      case Term(_, value) => value match {
        case "Android" => Android()
        case "iOS" => iOS()
        case "Windows" => Windows()
      }
    })

    implicit val deviceBuilder: Builder[Device] = build[Device] (node => (node: @unchecked) match {
      case Tree(_, children) => Device(children(0).build[DeviceType], children(1).build[Boolean])
    })
  }


  // Data Structures...
  sealed trait Node {
    val name: String

    def prompt: String

    def build[A: Builder]: A = implicitly[Builder[A]].build(this)
  }

  case class Tree(name: String, children: IndexedSeq[Node]) extends Node {
    override def prompt = {
      val choices = children.zipWithIndex.map { case (node, idx) => s"${idx + 1}) ${node.name}" }.mkString("\n")
      s"$toString\n$choices\n"
    }

    override def toString = name + children.mkString("(", ", ", ")")
  }

  case class Term(name: String, value: String) extends Node {
    override def prompt = s"$name = "

    override def toString = s"$name = $value"
  }


  def main(args: Array[String]): Unit = {

    val person = Tree("Person", IndexedSeq[Node](Term("name", "Fred"), Term("age", "45"),
      Tree("pet", IndexedSeq[Node](Term("name", "Fido")))))

    val device = Tree("some device", IndexedSeq[Node](Term("type", "iOS"), Term("on", "true")))

    println(person.prompt)
    println(person.toString)

    // TODO How to remove necessity of providing type parameter?
    println(person.build[Person])

    println(device.build[Device])

  }

}
bbarrington
  • 115
  • 7