Scala DSL - Nested block referencing parent

后端 未结 2 1435
慢半拍i
慢半拍i 2021-01-19 19:30

Playing about with a DSL in Scala, so lets say I have something like this:

house {
  floor {
    bedroom(\"kids)
    bedroom(\"master\")
  }
  floor {
    ki         


        
相关标签:
2条回答
  • 2021-01-19 20:20

    Do you really need each block to have a reference to the enclosing block? Or was it just so that you could add the nested block to the parent block? In this case you could simply pass nested blocks to the enclosing block, so to speak:

    house (
      floor (
        bedroom("kids"),
        bedroom("master")
      ),
      floor (
        kitchen
      )
    )
    

    Using the following definitions:

    trait HouseElement
    case class house( elements: HouseElement* )
    trait FloorElement
    case class floor( elements: FloorElement * ) extends HouseElement
    case class bedroom( name: String ) extends FloorElement
    case object kitchen extends FloorElement
    

    Otherwise, another solution is to rely heavily on anonymous classes (which unfortunately requires to use the new keyword everywhere):

    new house {
      new floor {
        new bedroom("kids")
        new bedroom("master")
      }
      new floor {
        new kitchen()
      }
    }
    

    Using the following definitions:

    import collection.mutable.Buffer
    class house {
      val elements = Buffer[Element]()
      trait Element {
        elements += this 
      }        
      class floor extends Element { 
        val elements = Buffer[Element]()
        trait Element {
          elements += this 
        }        
        class bedroom(name: String) extends Element 
        class kitchen extends Element
      }
    }
    
    0 讨论(0)
  • 2021-01-19 20:20

    This approach uses mutable fields to set the child-to-parent relation after the involved objects have been created:

    /* Data classes */
    
    class House(val floors: Seq[Floor])
    class Floor(val name: String, val bedrooms: Seq[Bedroom]) { var house: House = _}
    class Bedroom(val name: String) { var floor: Floor = _ }
    
    /* Factory methods */
    
    def house(floors: Floor*) = {
      val house = new House(floors)
      floors foreach (_.house = house)
      house
    }
    
    def floor(name: String)(bedrooms: Bedroom*) = {
      val floor = new Floor(name, bedrooms)
      bedrooms foreach (_.floor = floor)
      floor
    }
    
    def bedroom(name: String) = new Bedroom(name)
    

    This allows you to create house structures in a concise and type-safe way as follows:

    val myHouse = 
      house(
        floor("first")(
          bedroom("joe")
        ),
        floor("second")(
          bedroom("anna"),
          bedroom("clara")
        )
      )
    
    assert(myHouse.floors(0).house == myHouse)
    assert(myHouse.floors(1).house == myHouse)
    assert(myHouse.floors(0).bedrooms(0).floor == myHouse.floors(0))
    assert(myHouse.floors(1).bedrooms(1).floor == myHouse.floors(1))
    

    It should be fairly easy to factor out common behaviour into some base traits or methods, for example, iterating over the subcomponents to fix the relationships.

    0 讨论(0)
提交回复
热议问题