【问题标题】:idiomatic way to solve chicken-egg in scala在scala中解决鸡蛋的惯用方法
【发布时间】:2015-10-03 10:34:33
【问题描述】:

如何很好地初始化这样的结构:

case class A(name: String, b: B)
case class B(name: String, a: A)

寻找没有惰性验证(性能开销)并且不向现有案例类添加新成员(看起来很丑)的解决方案,但是特殊的包装器和原始类型签名的更改可能很好(至少这是我所拥有的最好的现在得到)。 toString-problem 可以忽略不计,因为我可以在某些特征中覆盖它。

【问题讨论】:

    标签: scala


    【解决方案1】:

    现在,我想出了这个:

       case class Chicken[T](h: Holder[T, _]) {
         def get = h.chicken
         override def toString = get.toString
       }
    
       case class Egg[U](h: Holder[_, U]) {
         def get = h.egg
         override def toString = get.toString
       }
    
       implicit def egg[T] = (_: Egg[T]).get
       implicit def chicken[T] = (_: Chicken[T]).get
    
       case class Holder[U, T] (chickenF: (Egg[T], Chicken[U]) => (U, T)) {
         val (chicken, egg) = chickenF( Egg(this), Chicken(this))
       }
    
       def mutual[U, T](chickenF: (Egg[T], Chicken[U]) => (U, T)) = {
            val h = new Holder(chickenF)
            h.chicken -> h.egg
       }
    
    
       trait Named { //to avoid unfinite toString
           def name: String
           override def toString = name
       }
    

    用法:

    case class A(name: String, b: Egg[B])
    case class B(name: String, a: Chicken[A]) extends Named
    
    val (a, b) = mutual[A, B](A("a", _) -> B("b", _))
    
    val (a2, b2) = mutual[A, B]{ (b2, a2) => //alternative for complex cases
        A("a", b2) -> B("b", a2)    
    }
    
    println(a)
    println(b)
    println(a.b)
    println(b.a)
    

    结果:

    A(a,b)                                                                                                                                                                                                                                                 
    b                                                                                                                                                                                                                                                      
    b                                                                                                                                                                                                                                                      
    A(a,b) 
    

    仍然希望有一个库来解决这个问题,比如 scalaz 或基于某些宏的解决方案。

    【讨论】:

      【解决方案2】:

      我发现不久前处理这个问题的另一种方法(虽然我现在更喜欢设计我的类型,使得这些循环引用不会出现),是为这些值传递一个生成器函数,而不是值直接(或像您的 ChickenEgg 类型那样替代)。例如:

      trait Named { //to avoid infinite toString, as before
        def name: String
        override def toString = name
      }
      
      case class A(name: String, generator: A => B) extends Named { val b: B = generator(this) }
      case class B(name: String, a: A) extends Named // Either or both types could extend Named
      

      示例用法:

      scala> def generator(name: String)(a: A): B = B(name, a)
      generator: (name: String)(a: A)B
      
      scala> val a = A("a", generator("b"))
      a: A = a
      
      scala> a.b
      res6: B = b
      

      【讨论】:

      • 我没有安装 scala REPL 来检查它是否正确推断出一个类型(看到我丑陋的 printlns 用于在线解释器,哈?:)),但使用 val a = A 可能会更好(“a”,B(“名称”,_))。对于像只涉及两种类型的特殊情况,我会将生成器和 b 移动到某些特征中(F-bounded 可能因为它应该知道 A)。如果它有效,您的解决方案似乎更好。附:实际上,我也从未遇到过,只是看到了几个问题,当有人想要同时拥有父/子链接以快速查找它们并以更方便的方式导航树时。
      • 顺便说一句,我的版本中的 egg 和 chicken 只是生成器的链接
      • @dk14 哦,是的,当然,您可以使用B("name", _) 作为生成器值;您可以将它或任何外部生成器函数包装到工厂方法、类或特征中;等
      猜你喜欢
      • 2017-02-28
      • 1970-01-01
      • 1970-01-01
      • 2018-11-09
      • 2017-03-08
      • 2011-01-02
      • 2016-01-01
      • 2017-02-21
      • 2010-10-21
      相关资源
      最近更新 更多