F#:在Map/Reduce/Filter中消除冗余

Li *_*oyi 13 f# functional-programming pipe

假设我有一个列表和一组,我想用它们做一些事情:

let outA = inA |> List.map(fun x -> x + 1) |> List.filter(fun x -> x > 10)
let outB = inB |> Set.map(fun x -> x + 1) |> Set.filter(fun x -> x > 10)
Run Code Online (Sandbox Code Playgroud)

现在,显然A处理列表和B处理集.然而,我发现必须List. List.反复写一遍非常烦人:不仅是冗长,重复的样板,它不传达任何信息并妨碍了阅读代码的功能,它也是一个事实上的类型注释我必须跟踪并与我的其余代码保持同步.

我想要做的是如下:

let outA = inA |> map(fun x -> x + 1) |> filter(fun x -> x > 10)
let outB = inB |> map(fun x -> x + 1) |> filter(fun x -> x > 10)
Run Code Online (Sandbox Code Playgroud)

编译器知道inA是一个列表而inB是一个集合,因此所有操作都来自正确的类,因此outA是一个列表,outB是一个集合.我可以通过Seq部分实现这一目标:

let map(x) =
    Seq.map(x)

let filter(x) =
    Seq.filter(x)
Run Code Online (Sandbox Code Playgroud)

我可以写出这一点.这个问题是它将所有内容都转换为Sequences,我不能再对它们执行列表/设置操作.同样的,

let outA = inA.Select(fun x -> x + 1).Where(fun x -> x > 10)
let outB = inB.Select(fun x -> x + 1).Where(fun x -> x > 10)
Run Code Online (Sandbox Code Playgroud)

同时删除所有这些,但随后将所有内容转换为IEnumerable.通过扩展将所有静态方法转换为实例方法,我设法让它变得非常好:

type Microsoft.FSharp.Collections.List<'a> with
    member this.map(f) = this |> List.map(f)
    member this.filter(f) = this |> List.filter(f)

let b = a.map(fun x -> x + 1).filter(fun x -> x > 10)
Run Code Online (Sandbox Code Playgroud)

但我怀疑这会遇到这里提到的类型推断问题:方法链接vs |>管道操作员?我真的不知道; 我不熟悉类型推断算法的工作原理.

最重要的是,我想我将会做很多这些list/set/array map/reduce/filter操作,我想让它们看起来尽可能漂亮和干净.现在,除了让我分心于表达式中的重要部分(即"地图"和lambda)之外,它们还在编译器应该快乐地知道我的集合是什么的地方提供了事实上的类型注释.传入是.

而且,这正是OO继承和多态性要解决的问题,所以我想知道是否存在一些我不知道的等效功能模式,它会像优雅一样解决它.也许我可以在自己的静态中map进行类型检查并map在输入上执行相关类型的函数?

有没有人有比我已经尝试过的更好的解决方案,或者我对F#类型推理引擎的基本限制感到头疼,我应该接受并继续前进?

Bri*_*ian 11

这不是OO /继承/多态解决的问题.而是'类型类'(la Haskell)解决的问题..NET类型系统不能执行类型类,并且F#不会尝试添加该功能.(你可以做一些毛茸茸的技巧inline,但它有各种限制.)

我建议接受它并继续前进.

  • 在较高的层面上,您的描述是正确的,似乎OO会这样做.但是在细节中,带有子类型的类型无法解决...请查看类型类,但也可能会看到"方差"(协方差和逆变),因为它与OO有关,以便了解为什么OO不能一般来说这很好解决. (3认同)
  • +1,但我认为支持类型类可以在.NET上完成,只是F#的类型系统没有实现这一点. (2认同)
  • 实际上,我并不是真的相信类型类会解决问题.F#(seq,list,array)中的不同集合都具有稍微不同的功能(tail,foldBack,zip,...),其中一些只能由某些集合合理支持.类型类意味着您必须为每组受支持的函数命名,您必须选择正确的粒度和继承.在Haskell中,"Functor"和"Monad"(及相关)的设计表明,这样做是非常困难的. (2认同)

Tom*_*cek 5

我同意布莱恩 - 你会习惯于此.正如我在上一个问题的答案中提到的,这有时非常有用,因为你会看到你的代码做了什么(哪个部分懒惰地评估,哪个部分使用数组来提高效率等)

此外,某些功能仅适用于某些类型 - 例如存在List.tailList.foldBack,但IEnumerable(Seq模块中)不存在类似功能- 原因很充分,因为它们会导致错误的代码.

在Haskell中,类型类可以描述具有某些函数的类型(例如mapfilter),但我认为它们不能很好地扩展 - 为F#库指定类型类,最终会得到指定列表的类型类层次结构样的数据结构,与列表类似的数据结构tailfoldBack式的功能,太多的其他限制.

除此之外,对于许多简单的列表处理工作,您还可以使用能够提供更好语法的理解(但当然,它仅对基本事物有用):

let outB = seq { for x in inA do 
                   if x > 10 do yield x + 1 }
Run Code Online (Sandbox Code Playgroud)