【问题标题】:How does this recursive List flattening work?这个递归列表展平如何工作?
【发布时间】:2011-08-27 07:28:31
【问题描述】:

不久前 this was asked and answered 在 Scala 邮件列表中:

凯文:

给定一些嵌套结构:List[List[...List[T]]] 将其展平为List[T]的最佳(最好是类型安全)方法是什么@

杰斯珀:

隐式参数和默认参数的组合有效:

case class Flat[T, U](fn : T => List[U]) 

implicit def recFlattenFn[T, U](implicit f : Flat[T, U] = Flat((l : T) 
=> List(l))) = 
   Flat((l : List[T]) => l.flatMap(f.fn)) 

def recFlatten[T, U](l : List[T])(implicit f : Flat[List[T], U]) = f.fn(l) 

例子:

scala> recFlatten(List(1, 2, 3)) 
res0: List[Int] = List(1, 2, 3) 

scala> recFlatten(List(List(1, 2, 3), List(4, 5))) 
res1: List[Int] = List(1, 2, 3, 4, 5) 

scala> recFlatten(List(List(List(1, 2, 3), List(4, 5)), List(List(6, 7)))) 
res2: List[Int] = List(1, 2, 3, 4, 5, 6, 7) 

我研究这段代码已经有一段时间了。我无法弄清楚它是如何工作的。似乎涉及一些递归......有人可以阐明吗?这种模式还有其他例子吗?它有名字吗?

【问题讨论】:

    标签: scala implicit


    【解决方案1】:

    哇哦,这是一个旧的!我将首先清理一下代码并使其符合当前的惯用约定:

    case class Flat[T, U](fn: T => List[U]) 
    
    implicit def recFlattenFn[T, U](
      implicit f: Flat[T, U] = Flat((xs: T) => List(xs))
    ) = Flat((xs: List[T]) => xs flatMap f.fn)
    
    def recFlatten[T, U](xs: List[T3])(implicit f: Flat[List[T], U]) = f fn xs 
    

    然后,事不宜迟,分解代码。首先,我们有我们的Flat 类:

    case class Flat[T, U](fn: T => List[U]) 
    

    这只不过是函数T => List[U] 的命名包装器,当给定T 类型的实例时,该函数将构建List[U]。注意这里的T也可以是List[U],也可以是U,或者List[List[List[U]]]等。一般情况下,这样的函数可以直接指定为参数的类型。但是我们将在隐式中使用这个,因此命名包装器避免了任何隐式冲突的风险。

    然后,从recFlatten 向后工作:

    def recFlatten[T, U](xs: List[T])(implicit f: Flat[List[T], U]) = f fn xs 
    

    此方法将采用xsList[T])并将其转换为U。为了实现这一点,它定位了Flat[T,U] 的隐式实例并调用封闭的函数fn

    然后,真正的魔法:

    implicit def recFlattenFn[T, U](
      implicit f: Flat[T, U] = Flat((xs: T) => List(xs))
    ) = Flat((xs: List[T]) => xs flatMap f.fn)
    

    这满足recFlatten要求的隐式参数,它还需要另一个隐式参数。最关键的是:

    • recFlattenFn 可以作为自己的隐式参数
    • 它返回一个Flat[List[X], X],所以recFlattenFn只会在TList时被隐式解析为Flat[T,U]
    • 如果隐式解析失败(即T 不是List),隐式f 可以回退到默认值

    也许这在其中一个例子的上下文中是最好的理解:

    recFlatten(List(List(1, 2, 3), List(4, 5))) 
    
    • T 类型被推断为List[List[Int]]
    • 尝试对 `Flat[List[List[Int]], U] 进行隐式查找
    • 这与递归定义的recFlattenFn 匹配

    广义地说:

    recFlattenFn[List[List[Int]], U] ( f =
      recFlattenFn[List[Int], U] ( f =
        Flat[Int,U]((xs: T) => List(xs)) //default value
      )
    )
    

    请注意,recFlattenFn 只会匹配对 Flat[List[X], X] 的隐式搜索,而类型参数 [Int,_] 则无法匹配,因为 Int 不是 List。这就是触发回退到默认值的原因。

    类型推断也可以逆向运行该结构,在每个递归级别解析 U 参数:

    recFlattenFn[List[List[Int]], Int] ( f =
      recFlattenFn[List[Int], Int] ( f =
        Flat[Int,Int]((xs: T) => List(xs)) //default value
      )
    )
    

    这只是Flat 实例的嵌套,每个实例(最里面的除外)都执行flatMap 操作以展开嵌套List 结构的一层。最里面的Flat 只是将所有单独的元素包装在一个List 中。

    Q.E.D.

    【讨论】:

    • 感谢您的帮助。我认为在您的示例中,类型参数被包装关闭了。这编译recFlatten[List[Int],Int](List(List(1, 2, 3), List(4, 5))) ( recFlattenFn[List[Int], Int] ( f = recFlattenFn[Int, Int] ( f = Flat[Int,Int]((xs: Int) => List(xs)) //default value ) ) )
    【解决方案2】:

    可能是一个很好的解决方案是尝试查看类型是如何推断的。为避免歧义,让我们重命名泛型:

    case class Flat[T, U](fn : T => List[U]) 
    
    implicit def recFlattenFn[T2, U2](implicit f : Flat[T2, U2] = 
                                        Flat((l : T2) => List(l))) = 
      Flat((l : List[T2]) => l.flatMap(f.fn)) 
    
    def recFlatten[T3, U3](l : List[T3])(implicit f : Flat[List[T3], U3]) = f.fn(l) 
    

    在第一种情况下,res0T3 的类型是 Int,你还不能推断 U3 的类型,但是你知道你需要一个隐式提供的 Flat[List[Int, U3]] 对象.只有一个“隐式候选者”:recFlattenFn 函数的结果,其类型为Flat[List[T2], List[U2]]。因此T2 = IntU2 = U3(我们仍然需要推断)。

    现在,如果我们希望能够使用recFlatten,我们必须为其提供一个参数f这是诀窍。您可以使用 Flat[Int, U2] 类型的隐式 类型 Int => List[Int] 的默认值。让我们看看可用的隐式。如前所述,recFlattenFn 可以提供一个Flat[List[T2], U2](用于新的T2U2)对象。此时它不符合f 的预期签名。因此,这里没有隐式是一个好的候选者,我们必须使用默认参数。由于默认参数的类型是 Int => List[Int],U2U3Int,然后就可以了。

    希望这篇长文能有所帮助。我把res1res2 的分辨率留给你。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-09-10
      • 2013-03-20
      • 2011-03-29
      • 2011-07-21
      • 2020-07-28
      • 1970-01-01
      • 2021-07-12
      • 2011-04-24
      相关资源
      最近更新 更多