Friday, March 16, 2018

Functional Adventures in F# - Types with member functions

Lets look at how to structure functions into the types that they are linked to, this helps with keeping the code base clean.

This post is part of a series:
Functional Adventures in F# - A simple planner
Functional Adventures in F# - Calling F# from C#
Functional Adventures in F# - Using Map Collections
Functional Adventures in F# - Application State
Functional Adventures in F# - Types with member functions
Functional Adventures in F# - Getting rid of loops
Functional Adventures in F# - Getting rid of temp variables
Functional Adventures in F# - The MailboxProcessor
Functional Adventures in F# - Persisting Application State
Functional Adventures in F# - Adding Snapshot Persisting
Functional Adventures in F# - Type-safe identifiers
Functional Adventures in F# - Event sourcing lessons learned


I started converting my small but handy 3D math library to F# and very quickly I ran into a problem. Not that unexpected as I am a beginner with F#.
So say that you have some types:
type Vector3 = 
 { 
  X:float
  Y:float
  Z:float 
 }
type Vector4 = 
 { 
  X:float
  Y:float
  Z:float 
  W:float 
 }
That in the end will have quite similar functionality like .. for example Add, Divide etc. With the knowledge that I have so far of F# I ended up with
let addVector3 (value1:Vector3) (value2:Vector3) = 
 {
  X = value1.X + value2.X
  Y = value1.Y + value2.Y
  Z = value1.Z + value2.Z
 }
let addVector4 (value1:Vector4) (value2:Vector4) = 
 {
  X = value1.X + value2.X
  Y = value1.Y + value2.Y
  Z = value1.Z + value2.Z
  W = value1.W + value2.W
 }
I quickly thought that there must be a cleaner way to write this. I like tidy code, neat code.. not code that just looks bad and will turn into a maintenance hell even before it is shipped.
Luckily it turns out you can define functions as members of types. So the above 2 functions would look like:
type Vector3 = 
 { 
  X:float
  Y:float
  Z:float 
 }
 member this.add (value2:Vector3) = 
 {
  X = this.X + value2.X
  Y = this.Y + value2.Y
  Z = this.Z + value2.Z
 }
type Vector4 = 
 { 
  X:float
  Y:float
  Z:float 
  W:float 
 }
 member this.add (value2:Vector4) = 
 {
  X = this.X + value2.X
  Y = this.Y + value2.Y
  Z = this.Z + value2.Z
  W = this.W + value2.W
 }
Here, both are called just add, and they can be invoked directly on the objects.
let value1 = { X = 1.0; Y = 1.0; Z = 1.0 }
let value2 = { X = 2.0; Y = 2.0; Z = 2.0 }
let added = value1.add value2
A little cumbersome syntax if you want to do more complex calculations. So lets look at operator overloading in F# to so that we can get nice arithmetic operators into play for our new types
type Vector3 = 
 { 
  X:float
  Y:float
  Z:float 
 }
 static member (+) (value1:Vector3, value2:Vector3) = 
  {
   X = value1.X + value2.X
   Y = value1.Y + value2.Y
   Z = value1.Z + value2.Z
  }
 static member maxValue = { X = Microsoft.FSharp.Core.float.MaxValue; Y = Microsoft.FSharp.Core.float.MaxValue; Z = Microsoft.FSharp.Core.float.MaxValue; } 

 member this.add (value2:Vector3) = this + value2
This lets us write something like this instead:
let added = value1 + value2
Also note the static member maxValue that is a static function that is invoked by Vector3.maxValue, in this case it just returns the max vector but it can be any function.

So there.

Update: evidently static members should start with capital letter according to design guide-lines. Makes sense as they are easier to spot.

All code provided as-is. This is copied from my own code-base, May need some additional programming to work. Use for whatever you want, how you want! If you find this helpful, please leave a comment, not required but appreciated! :)

Hope this helps someone out there!
Until next time: Work to Live, Don’t Live to Work

No comments:

Post a Comment