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

Mas*_*tic 4 c# oop f# overriding functional-programming

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

例如,为了工作,list你可以使用中发现的功能List模块,工作与option有与Option模块等

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

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

如何在函数式编程中?

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

Dan*_*ian 5

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

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

更接近于有区别联盟的FP风格功能更像是访客模式.模式匹配是访问者完成的动态调度的(严格)更强版本.

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

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

type 'a Collection =
    | Array of 'a array
    | List of 'a list
    | Seq of 'a seq
Run Code Online (Sandbox Code Playgroud)

在这种情况下,你总是可以集合包装成三种情况中的一种,你可以为这两种情况提供特殊的实现,你可以做一些有意义的事情,例如List,Array同时为更简单的情况提供一些默认值.我喜欢将DU视为一个案例的定义.通常,分支逻辑非常适合这种世界观.所以我会nth以这样的方式编写一个函数,我会处理这些情况,我可以有意义地做些什么.留下我对默认情况不了解的情况.

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

请看以下内容:

[1..10] |> List.sumBy (+)
[1.0..10.0] |> List.sumBy (+)
Run Code Online (Sandbox Code Playgroud)

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

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

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


Gus*_*Gus 5

您描述的具有基类的覆盖方案与子类型多态性相关联.在你的情况下,你可以有一个共同的界面,如IEnumerable.这种多态性在OOP中非常常见,但在FP中则不然,它在其他两种类型的多态性上传递更多:Ad-Hoc和Parametric.

一个例子是Type Classes,在Haskell中你可以让一个Function接受任何类型为T的参数,只要类型T是一个类型C的实例,但你的Function可以用一般的方式实现>你还可以通过添加仅对该特定实例T起作用的Function来"覆盖"该泛型定义,当使用该特定类型调用此类函数时,将覆盖一般实现,请注意这将在编译时而不是编译时知道.

一个简单的例子是与函子和单子,该fmap功能可以在任何单子一个通用的方法来实现在功能方面bindreturn,但您可以添加的具体实施fmap,以list和/或option比一般的一个更有效.

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

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

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

UPDATE

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

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
Run Code Online (Sandbox Code Playgroud)

但没有[]的覆盖:

MyType.Nth bigArr 9000000
// General case
// Real: 00:00:00.052, CPU: 00:00:00.046, GC gen0: 0, gen1: 0, gen2: 0
Run Code Online (Sandbox Code Playgroud)

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

通过使用.NET重载方法,您可以覆盖,但您可能会争辩说,由于在调用站点处解决了重载,因此无法进行远程操作,因此您无法在该调用之上定义另一个泛型函数.

现在让我们假设我有一个Type类Collection,它表示可以升级的所有类型,如果我在F#中有类型类,我将能够编写类似seq的函数nth:

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

// Specific implementation for arrays
let nth n (x:_ []) = x.[n]
Run Code Online (Sandbox Code Playgroud)

不幸的是,这不会编译,因为F#中的重载只适用于方法,而不是函数,而且目前F#中没有类型类,但正如我所提到的有一些变通方法,使用FsControl我可以写这个实际编译和允许我运行相同的测试.

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