Nick
Nick

Reputation: 9051

Functional way to write these methods in F#

In order to calculate the area of square and circle, I defined the following type:

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

When I read this article: http://fsharpforfunandprofit.com/posts/type-extensions/

It states:

So, a plea for those of you new to functionally programming. Don't use methods at all if you can, especially when you are learning. They are a crutch that will stop you getting the full benefit from functional programming.

Then what's the functional way to solve this problem? or what's the idomatic F# way?


Edit:

After reading the "The F# Component Design Guidelines" (curtsy to @V.B.), and @JacquesB's comment, I consider that implement the member method within the type is the most simple, intrinsic way:

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

(This is almost identical with my original Square type -- this Square2 only saves seveal this. prefix as in this.width, this.length.)

Again, the The F# Component Design Guidelines is quite useful.

Upvotes: 6

Views: 268

Answers (2)

V.B.
V.B.

Reputation: 6382

There is a more functional way that @svick describes well, but consider also "The F# Component Design Guidelines"

  • Do use properties and methods for operations intrinsic to types.

This is called out specifically because some people from a functional programming background avoid the use of object oriented programming together, preferring a module containing a set of functions defining the intrinsic functions related to a type (e.g. length foo rather than foo.Length). But see also the next bullet. In general, in F#, the use of object-oriented programming is preferred as a software engineering device. This strategy also provides some tooling benefits such as Visual Studio’s “Intellisense” feature to discover the methods on a type by “dotting into” an object.

  • Consider using interface types to represent related groups of operations that may be implemented in multiple ways.

In F# there are a number of ways to represent a dictionary of operations, such as using tuples of functions or records of functions. In general, we recommend you use interface types for this purpose.

So, according to these guidelines, an interface IShape with Area and Perimeter members is a recommended way for an F# component, despite there is a "more functional" way in general.

Upvotes: 1

svick
svick

Reputation: 244777

A more functional way to do this would be to create a Shape discriminated union, where Square and Circle would be its cases. Then create functions area and perimeter, taking Shape and using pattern matching:

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)

Upvotes: 8

Related Questions