开发者

How to get Map-like sugar in another constructor

开发者 https://www.devze.com 2023-02-02 11:06 出处:网络
What I need is a class X I can construct with a Map that takes Strings into either other Strings or Maps that take Strings into Strings, and then an arbitrary number of other instances of X.With my li

What I need is a class X I can construct with a Map that takes Strings into either other Strings or Maps that take Strings into Strings, and then an arbitrary number of other instances of X. With my limited grasp of Scala, I know I can do this:

class Person (stringParms : Map[String, String],
        mapParms : Map[String, Map[String, String]],
        children : List[X]) {
}

but that doesn't look very Scala-ish ("Scalish"? "Scalerific"? "Scalogical"?) I'd like to be able to do is the following:

Person bob = Person("name" -> "Bob", "pets" -> ("cat" -> "Mittens", "dog" -> "Spot"), "status" -> "asleep", 
        firstChild, secondChild)

I know I can get rid of the "new" by using the companion object and I'm sure I can look Scala varargs. What I'd like to know is:

  1. How I can use -> (or some similarly plausible operator) to construct elements to be made into a Map in the construction?
  2. How I can define a single map so either it can do an Option-like switch between two very disparate types or becomes a recursive tree, where each (named) node points to either a l开发者_如何学Pythoneaf in the form of a String or another node like itself?

The recursive version really appeals to me because, although it doesn't address a problem I actually have today, it maps neatly into a subset of JSON containing only objects and strings (no numbers or arrays).

Any help, as always, greatly appreciated.


-> is just a syntactic sugar to make a pair (A, B), so you can use it too. Map object takes a vararg of pairs:

def apply [A, B] (elems: (A, B)*) : Map[A, B]

You should first check out The Architecture of Scala Collections if you're interested in mimicking the collections library.

Having said that, I don't think the signature you proposed for Person looks like Map, because it expects variable argument, yet children are not continuous with the other (String, A) theme. If you say "child1" -> Alice, and internally store Alice seperately, you could define:

def apply(elems: (String, Any)*): Person

in the companion object. If Any is too loose, you could define PersonElem trait,

def apply(elems: (String, PersonElem)*): Person

and implicit conversion between String, Map[String, String], Person, etc to PersonElem.


This gets you almost there. There is still a Map I don't get easily rid of.

The basic approach is to have a somewhat artificial parameter types, which inherit from a common type. This way the apply method just takes a single vararg.

Using implicit conversion method I get rid of the ugly constructors for the parameter types

case class Child

case class Person(stringParms: Map[String, String],
    mapParms: Map[String, Map[String, String]],
    children: List[Child]) { }

sealed abstract class PersonParameter 
case class MapParameter(tupel: (String, Map[String, String])) extends PersonParameter 
case class StringParameter(tupel: (String, String)) extends PersonParameter 
case class ChildParameter(child: Child) extends PersonParameter

object Person {
    def apply(params: PersonParameter*): Person = {

        var stringParms = Map[String, String]()
        var mapParms = Map[String, Map[String, String]]()
        var children = List[Child]()
        for (p ← params) {
            p match {
                case StringParameter(t) ⇒ stringParms += t
                case MapParameter(t) ⇒ mapParms += t
                case ChildParameter(c) ⇒ children = c :: children
            }
        }
        new Person(stringParms, mapParms, children)
    }
    implicit def tupel2StringParameter(t: (String, String)) = StringParameter(t)
    implicit def child2ChildParameter(c: Child) = ChildParameter(c)
    implicit def map2MapParameter(t: (String, Map[String, String])) = MapParameter(t)

    def main(args: Array[String]) {
        val firstChild = Child()
        val secondChild = Child()
        val bob: Person = Person("name" -> "Bob","pets" -> Map("cat" -> "Mittens", "dog" -> "Spot"),"status"
-> "asleep", 
        firstChild, secondChild)

        println(bob)
    } }


Here's one way:

sealed abstract class PersonParam
object PersonParam {
    implicit def toTP(tuple: (String, String)): PersonParam = new TupleParam(tuple)
    implicit def toMap(map: (String, Map[String, String])): PersonParam = new MapParam(map)
    implicit def toSP(string: String): PersonParam = new StringParam(string)
}

class TupleParam(val tuple: (String, String)) extends PersonParam
class MapParam(val map: (String, Map[String, String])) extends PersonParam
class StringParam(val string: String) extends PersonParam

class Person(params: PersonParam*) {
    val stringParams = Map(params collect { case parm: TupleParam => parm.tuple }: _*)
    val mapParams = Map(params collect { case parm: MapParam => parm.map }: _*)
    val children = params collect { case parm: StringParam => parm.string } toList
}

Usage:

scala> val bob = new Person("name" -> "Bob",
     | "pets" -> Map("cat" -> "Mittens", "dog" -> "Spot"),
     | "status" -> "asleep",
     | "little bob", "little ann")
bob: Person = Person@5e5fada2

scala> bob.stringParams
res11: scala.collection.immutable.Map[String,String] = Map((name,Bob), (status,asleep))

scala> bob.mapParams
res12: scala.collection.immutable.Map[String,Map[String,String]] = Map((pets,Map(cat -> Mittens, dog -> Spot)))

scala> bob.children
res13: List[String] = List(little bob, little ann)
0

精彩评论

暂无评论...
验证码 换一张
取 消