假设我有一个列表和一组,我想用它们做一些事情:
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)
现在,显然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)
编译器知道inA是一个列表而inB是一个集合,因此所有操作都来自正确的类,因此outA是一个列表,outB是一个集合。我可以通过Seq:
部分实现这一目标let map(x) =
Seq.map(x)
let filter(x) =
Seq.filter(x)
我可以写出来。这个问题是它将所有内容都转换为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)
同时删除所有内容,但随后将所有内容强制转换为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)
但我怀疑会遇到这里提到的类型推断问题:Method Chaining vs |> Pipe Operator?我真的不知道;我不熟悉类型推断算法的工作原理。
最重要的是,我想我将会做很多这些list / set / array map / reduce / filter操作,我想让它们看起来尽可能漂亮和干净。现在,除了让我分心于表达式中的重要部分(即“地图”和lambda)之外,它们还在编译器应该快乐地知道我的集合的地方提供了事实上的类型注释。传入是。
此外,这正是OO继承和多态意味着要解决的问题,所以我想知道是否有一些我不知道的等效功能模式会像优雅一样解决它。也许我可以在自己的静态map
中进行类型检查,并在输入上执行相关类型的map
函数?
有没有人有比我已经尝试过的更好的解决方案,或者我是否对F#类型推理引擎的基本限制感到头疼,我应该接受并继续前进?
答案 0 :(得分:11)
这不是OO /继承/多态解决的问题。而是'类型类'(la Haskell)解决的问题。 .NET类型系统不能执行类型类,并且F#不会尝试添加该功能。 (您可以使用inline
做一些毛茸茸的技巧,但它有各种限制。)
我建议接受并继续前进。
答案 1 :(得分:5)
我同意Brian的意见 - 你只会习惯这个。正如我在上一个问题的答案中提到的,这有时非常有用,因为你会看到你的代码做了什么(哪个部分懒惰地评估,哪个部分使用数组来提高效率等等。)
此外,某些功能仅适用于某些类型 - 例如List.tail
或List.foldBack
,但IEnumerable
不存在类似的功能(在{{1}中) }模块) - 因为它们会导致错误的代码。
在Haskell中,类型类可以描述具有某些函数的类型(如Seq
和map
),但我认为它们不能很好地扩展 - 为F#库指定类型类,最终会得到一个类型类层次结构,它指定类似列表的数据结构,类似于列表的数据结构filter
和tail
- 类似函数和太多其他约束。
除此之外,对于许多简单的列表处理工作,您还可以使用能够提供更好语法的理解(但当然,它仅对基本事物有用):
foldBack