【问题标题】:Functional way to write these methods in F#在 F# 中编写这些方法的函数式方法
【发布时间】:2015-07-22 03:17:29
【问题描述】:

为了计算正方形和圆形的面积,我定义了以下类型:

type Square = {width: float; length: float;} with
    member this.area = this.width * this.length
    member this.perimeter = (this.width + this.length) * 2.

type Circle = {r:float} with 
    member this.area = System.Math.PI * this.r * this.r
    member this.perimeter = 2. * System.Math.PI * this.r

let s1 = {width = 3.; length = 4.}
let c1 = {r = 8.3}

printfn "%A" s1
printfn "The area of s1 is: %A" s1.area
printfn "The perimeter of s1 is: %A" s1.perimeter


printfn "%A" c1
printfn "The area of c1 is: %A" c1.area
printfn "The perimeter of c1 is: %A" c1.perimeter

当我读到这篇文章时: http://fsharpforfunandprofit.com/posts/type-extensions/

它说:

  • 方法不能很好地用于类型推断
  • 方法不能很好地处理高阶函数

所以,恳请那些刚接触函数式编程的人。不要使用 如果可以的话,尤其是在你学习的时候。他们是 一个拐杖会阻止你从功能中获得全部好处 编程。

那么解决这个问题的函数式方法是什么?或者什么是惯用的 F# 方式?


编辑

在阅读了“F# 组件设计指南”(对@V.B. 屈膝)和@JacquesB 的评论之后,我认为在类型中实现成员方法是最简单、内在的方式:

type Square2 (width: float, length: float) =
    member this.area = width * length
    member this.perimeter = (width + length) * 2.

(这与我原来的Square 类型几乎相同——这个Square2 只保存了几个this. 前缀,如this.widththis.length。)

同样,The F# Component Design Guidelines 非常有用。

【问题讨论】:

  • 更多功能不一定更符合 F# 的习惯! F# 是一种面向对象/函数式的语言,适当地使用方法就可以了。当您已经了解 oo 时,这句话实际上是关于 学习 函数式编程,在这种情况下,尝试尽可能地函数式编程是一个很好的学习练习。
  • 关于当前的两个答案以及反对“方法”的请求,当您预先了解所有案例时,请使用有区别的工会。这更实用,并且确实更好地映射了您的域——这 N 个选项是我唯一的案例——您将通过它了解更多 F#。当您希望能够通过您的程序临时定义新的实现时,请使用接口(和方法)。

标签: f# idioms


【解决方案1】:

更实用的方法是创建一个Shape 可区分联合,其中SquareCircle 就是它的情况。然后创建函数areaperimeter,取Shape 并使用模式匹配:

type Shape =
    | Square of Width: float * Length: float
    | Circle of R: float

let area = function
    | Square (width, length) -> width * length
    | Circle r -> System.Math.PI * r * r

let perimeter = function
    | Square (width, length) -> (width + length) * 2.
    | Circle r -> 2. * System.Math.PI * r

let s1 = Square(Width = 3., Length = 4.)
let c1 = Circle(R = 8.3)

printfn "%A" s1
printfn "The area of s1 is: %A" (area s1)
printfn "The perimeter of s1 is: %A" (perimeter s1)


printfn "%A" c1
printfn "The area of c1 is: %A" (area c1)
printfn "The perimeter of c1 is: %A" (perimeter c1)

【讨论】:

  • 为什么这更实用?请详细说明。这两种方法都没有副作用。
  • @rightfold 鉴于其实现也缺乏副作用,人们只能假设问题的意图是“如何使用功能语言中比 OO 语言更常见的功能来编写这个”,这似乎成为当今“功能”的一种公认定义。 (正如非字面意思是字面意思的一种公认定义一样。不要向信使开枪。)
  • @rightfold 问题中已经解释过了。例如let f x = x.area 不会编译,而let f x = area x 会。
【解决方案2】:

@svick 很好地描述了一种更实用的方式,但也可以考虑 "The F# Component Design Guidelines"

  • 对类型固有的操作使用属性和方法。

之所以这么叫,是因为有些人来自职能部门 编程背景避免使用面向对象编程 一起,更喜欢一个包含一组定义函数的模块 与类型相关的内在函数(例如长度 foo 而不是 foo.Length)。但另请参阅下一个项目符号。一般来说,在 F# 中,使用 面向对象的编程是首选的软件工程 设备。该策略还提供了一些工具优势,例如 Visual Studio 的“Intellisense”功能可发现 通过“打点”一个对象来输入。

  • 考虑使用接口类型来表示可能以多种方式实现的相关操作组。

在 F# 中有多种方式来表示 操作,例如使用函数元组或函数记录。 一般来说,我们建议您为此目的使用接口类型。

因此,根据这些准则,尽管通常有一种“功能更强大”的方式,但带有 AreaPerimeter 成员的接口 IShape 是 F# 组件的推荐方式。

【讨论】:

  • 我不认为这两个项目符号意味着需要一个接口。两者都可以通过类型扩展以更实用的方式来实现。实际上,后一个项目符号更多的是关于不记录函数,这可能很难理解。
  • @DaxFohl 与 svick 的回答进行比较时,我将 IShape 理解为“可以以多种方式实现的相关操作组” - 面积和周长。为每种情况使用单独函数的模式匹配距离函数记录并不遥远。
  • 另请注意,这些是组件设计指南,即它描述了库的公共表面应该是什么样子,它不影响实现。
  • 嗯,它是“考虑”,但也有“考虑在 F#-to-F# 代码和库中使用 F# 扩展成员”。我个人更喜欢这种方式,因为当您按 F12 进入源代码时,它会将您带到实际的实现,而不仅仅是接口声明。当您希望稍后在代码中定义新的实现时,接口很好;受歧视的工会迫使您预先定义它们。因此,最终的选择取决于哪个选项更适合自己的领域模型。
  • @svick 我明确表示,这个“考虑”是针对组件的,并将您的答案称为“功能方式”的正确答案。不过,根据我的经验,我喜欢正方形的实现在正方形类型中而不是在某些模块中。鉴于没有可变状态,并且这些是属性而不是方法,这样一个具有成员的类就像一个模块一样“功能性”。
猜你喜欢
  • 1970-01-01
  • 2014-01-20
  • 2019-04-20
  • 2011-03-09
  • 1970-01-01
  • 2012-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多