什么是函数式编程的最重要的答案?

时间:2014-04-01 15:13:34

标签: c# oop f# functional-programming override

我很熟悉功能编程和F#来自OOP& C#背景,我注意到在函数式编程方法中,常常是静态的,在模块中根据类型(我可以理解为什么)。

例如,要使用list,您可以使用List模块中的功能,与option模块一起使用Option等。

然而,当我接受基类的参数,并且派生类有更合适的实现时会发生什么?

在OOP中,将调用适当的实现,即派生实现,即使该对象静态地是基类,因为该成员已被重写

如何在函数式编程中使用?

如果你有基本类型作为参数(你应该),你只能使用它的功能,就像你只能在OOP中调用基地的成员一样。
唯一不同的是,在OOP中,被叫成员是合适的成员。

2 个答案:

答案 0 :(得分:5)

您不一定以这种方式对其进行建模。考虑一下;区分联合对应于整个类层次结构而不仅仅是一个类。因此,您在区分联合上定义的任何函数已经对应于基类上的方法,包括所有覆盖。情况就是如此,因为受歧视的联合会因数据案例而被关闭以进行扩展,但是对于添加新功能是开放的。

通常在OOP中,类会关闭以添加功能,但会打开以添加新的数据案例。

更接近对应于有区别的联盟的FP风格函数,就像访客模式一样。模式匹配是访问者完成的动态调度的(严格)更强版本。

此外,在F#中,gener扮演的角色比在C#中扮演的角色要大得多,因此除了从代码中逻辑上遵循的信息之外,您通常没有任何有关相关类型的信息。例如,您通常会根据基类使用代码,而是使用通用类型'a

编辑:关于编写返回第n个元素的函数。同样,您通常已经知道所有类型的集合,例如。

type 'a Collection =
    | Array of 'a array
    | List of 'a list
    | Seq of 'a seq

在这种情况下,您可以始终将集合包装成三种情况之一,并且您可以为这两种情况提供特殊实施,您可以使用例如ListArray虽然为更简单的案例提供了一些默认值。我喜欢将DU视为一个案例的定义。通常,分支逻辑非常适合这种世界观。所以我会以这样的方式写一个nth函数,我会处理这些情况,我可以有意义地做些什么。并且保留我不了解默认情况的案例。

在函数式编程中,你通过继承更少关注子类型多态性(典型的OOP风格,"我不知道在运行时会有什么具体实现"),而是参数多态(即泛型)ad-hoc多态(重载)。

请看以下内容:

[1..10] |> List.sumBy (+)
[1.0..10.0] |> List.sumBy (+)

这里,您在两种情况下都使用相同的+函数。即使它实际上是另一种类型。

每当你注意到某些代码在两个不同的地方是相同的时候,就有很好的机会,在FP中,你可以抽象出那段代码并使你的代码更通用和可重用。因此,即使像List.map之类的函数只是一个具体的函数,它实际上可以在非常大的上下文中使用,并且通过提供特定的行为来表现多态"即一个在该背景下需要的功能。

OOP样式多态性与那些高阶函数之间的区别在于,您可以在需要的地方传递所需的行为,而不是它是类层次结构的一部分。

答案 1 :(得分:5)

您描述的覆盖方案,具有基类与子类型Polymorphism相关联。在您的情况下,您可以使用通用界面,例如IEnumerable。 这种多态性在OOP中非常常见,但在FP中却不常见,它在其他两种类型的多态性上传递更多:Ad-Hoc和Parametric。

这样的一个例子是Type Classes,在Haskell中你可以让一个函数接受任何类型为T的参数,只要类型T是类型C的一个实例,你的函数可以用通用的方式实现>你仍然可以通过添加一个仅在特定实例T上工作的函数来“覆盖”该泛型定义,当使用该特定类型调用此函数时,将覆盖一般实现,请注意这将在编译时而不是编译时知道-time。

一个简单的例子是Functors和Monads,fmap函数可以在函数bindreturn方面以任何Monad的通用方式实现,但是你可以添加fmaplist和/或option的具体实施,比通用的更有效。

我不是100%肯定,但我认为在C#中你可以在Linq中进行这种优化,因为你可以为SelectMany定义更多的重载,相当于bind

不幸的是在F#中没有这样的内置机制,虽然有一些方法可以对此进行编码(参见FsControl中的默认方法)但F#不仅仅是功能,它是多范式并且存在于。 NET世界(OOP世界),因此您可以覆盖SubTyping就绪。

话虽如此,值得一提的是这更像是一种技术观点,但在大多数情况下,在设计方面与OOP的覆盖不是一对一的,因为这比SubTyping更通用。我的意思是,如果你将你的OOP设计迁移到FP并改变一个类层次结构让我们说一个Discriminated Union,那么你的泛型方法最有可能最终出现在match中的所有其他情况中(下划线 | _ -> )以及特定情况下的覆盖。

<强>更新

以下是评论中您的问题的答案:

type MyType = 
    static member Nth (x:seq<_>) = fun n -> printfn "General case"; Seq.nth n x 
    static member Nth (x:_ [])   = fun n -> printfn "Override for []"; x.[n]

let bigSeq = seq {1..10000000}
let bigLst = [ 1..10000000 ]
let bigArr = [|1..10000000|]

MyType.Nth bigSeq 9000000
// General case
// Real: 00:00:00.217, CPU: 00:00:00.218, GC gen0: 0, gen1: 0, gen2: 0

MyType.Nth bigArr 9000000
// Override for []
// Real: 00:00:00.001, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0

MyType.Nth bigLst 9000000
// General case
// Real: 00:00:00.080, CPU: 00:00:00.078, GC gen0: 0, gen1: 0, gen2: 0

但没有[]的覆盖:

MyType.Nth bigArr 9000000
// General case
// Real: 00:00:00.052, CPU: 00:00:00.046, GC gen0: 0, gen1: 0, gen2: 0

这就是ad-hoc多态(重载)中的重写方式。

通过使用.NET重载方法,你可以覆盖,但你可能会说你不能走远,因为在调用站点解决了重载,所以你不能在那个调用之上定义另一个泛型函数。

现在让我们假设我有一个类型类Collection代表可以升级到seq的所有类型,如果我在F#中有类型类,我会有一个函数nth我能够写点类似的东西:

// General implementation relaying in IEnumerable<_>
let nth n (x:Collection<_>) = Seq.nth n

// Specific implementation for arrays
let nth n (x:_ []) = x.[n]

不幸的是,这不会编译,因为F#中的重载只适用于方法而不是函数,而且目前F#中没有类型类,但正如我所提到的那样有一些解决方法,FsControl我可以编写this实际上会编译并允许我运行相同的测试。

无论如何都有相同的场景,你如何在OOP中编码,因为你无法访问Seq和Array的源代码?对于子类型多态,在这种情况下,您将无法覆盖任何内容。