Applied F# Symposium Session 2
How to Define Consumable Classes
Fine Points
1. F# is Free, C# is Closed – members in F# are public by default, members in C# are private by default. When in doubt, spell it out.
2. F# wants you to use lots of little static functions. Our customers don’t – they want objects/controls. Follow the template
3. F# has multiple, sometimes conflicting syntaxes for defining things. Sorry.
4. Files in an F# project are not necessarily in alphabetical order; they are in parse/necessity/dependency order. Again, sorry.
Content
Best practice point – here’s how to build a class file
Gross Structure
<namespace declaration>
<open clauses>
<helper modules>
<open clauses>
<class definition(s)>
Example:
namespace Frobozz.lifeform
open System
open System.IO
module internal argutils =
let raiseOnNull s o =
if o = null then raise(ArgumentNullException(s)) else o
open argutils
type FilePath(s:string) =
let _pathStr = raiseOnNull "s" s
let _path = _pathStr.Split([| '/'; '\\' |])
member this.Path with get() = _path
Exercise
1. Define a class called Digit which constructs with a string, its name, and has a read-only property Name and overrides ToString to print its name
2. Define a class called Hand which constructs with an int, the number of digits, and has a property which is a list of Digit, initialized to the digits. Every hand with n digits has n-1 fingers and 1 thumb.
3. Show and tell for abstract life forms, then make your favorite non-mute animal
4. Show and tell for interfaces and mix-in classes
namespace Frobozz.lifeform
open System
open System.IO
open System.Media
open System.Reflection
module internal utils =
let raiseOnNull s o =
if o = null then raise(ArgumentNullException(s))
else o
let raiseOnNegative s n =
if sign n < 0 then raise(ArgumentOutOfRangeException(s))
else n
open utils
type Digit(name:string) =
let _name = raiseOnNull "name" name
member this.Name with get() = _name
override this.ToString() = this.Name
module internal lifeHelpers =
let namedDigit (n:int) =
new Digit(String.Format("finger {0}", n))
let rec makeDigits n l =
if n = 0 then l
elif n = 1 then new Digit "thumb" :: l
else (namedDigit (n - 1)) :: (makeDigits (n - 1) l)
let makeDigits1 n =
if n > 0 then new Digit("thumb") :: [ for i in 1 .. (n-1) -> (namedDigit i) ]
else []
open lifeHelpers
type Hand(digitCount:int) =
let _digits = makeDigits digitCount []
member this.Digits with get() = _digits
[<AbstractClass>]
type Lifeform() =
abstract member Vocalize : unit -> unit
[<AbstractClass>]
type SoundLifeform() =
inherit Lifeform()
abstract member GetSoundStream : unit -> Stream
override this.Vocalize() =
let stm = this.GetSoundStream() |> raiseOnNull "GetSoundStream"
let player = new SoundPlayer(stm)
player.PlaySync()
type FSharpDuck(assem : Assembly, resName:string) =
inherit SoundLifeform()
override this.GetSoundStream() =
assem.GetManifestResourceStream(resName)
type IVocal =
abstract member Vocalize : unit -> unit
type BetterLifeform() =
abstract member Metabolize : unit -> unit
default this.Metabolize() = Console.WriteLine("breathing.")
member this.CanSpeak with get() = (this :> obj) :? IVocal
type Slug() =
inherit BetterLifeform()
type BetterDuck() =
inherit BetterLifeform()
interface IVocal with
override this.Vocalize() = Console.WriteLine("quack.")