Members

Additional elements in type definition (class, record, union)

  • (Event)

  • Method

  • Property

  • Indexed property

  • Operator overload

Static and instance members

Static member: static member member-name ....

Instance member:

  • Concrete member: member self-identifier.member-name ...

  • Abstract member: abstract member member-name: type-signature

  • Virtual member = requires 2 declarations

    1. Abstract member

    2. Default implementation: default self-identifier.member-name ...

  • Override virtual member: override self-identifier.member-name ...

☝ member-name in PascalCase (.NET convention)

☝ No protected member!

Self-identifier

  • In Cβ™―, Java, TypeScript : this

  • In VB : Me

  • In Fβ™― : we can choose β†’ this, self, me, any valid identifier...

Declaration:

  1. For the primary constructor❗: with as β†’ type MyClass() as self = ...

    • ⚠️ Can be costly

  2. For a member: member me.Introduce() = printfn $"Hi, I'm {me.Name}"

  3. For a member not using it: with _ (since Fβ™― 6) β†’ member _.Hi() = printfn "Hi!"

Call a member

Calling a static member β†’ Prefix with the type name: type-name.static-member-name

Calling an instance member inside the type β†’ Prefix with self-identifier: self-identifier.instance-member-name

Calling an instance member from outside the type β†’ Prefix with instance-name: instance-name.instance-member-name

Method

≃ Function attached directly to a type

2 forms of parameter declaration:

  1. Curried parameters = FP style

  2. Parameters in tuple = OOP style

    • Better interop with Cβ™―

    • Only mode allowed for constructors

    • Support named, optional, arrayed parameters

    • Support overloads

☝ with required in β‘  but not in β‘‘ because of indentation β†’ end can end the block started with with (not recommended)

☝ this.Price β’Ά and me.Price β’· β†’ Access to instance via self-identifier defined by member

Named arguments

Calls a tuplified method by specifying parameter names:

Useful to:

  • Clarify a usage for the reader or compiler (in case of overloads)

  • Choose the order of arguments

  • specify only certain arguments, the others being optional

☝ Arguments after a named argument must be named too.

Optional parameters

Allows you to call a tuplified method (including constructors) without specifying all the parameters.

Optional parameter:

  • Declared with ? in front of its name β†’ ?arg1: int

  • In the body of the method, wrapped in an Option β†’ arg1: int option

    • You can use defaultArg to specify the default value

    • But the default value does not appear in the signature!

When the method is called, the argument can be specified either:

  • Directly in its type β†’ Method(arg1 = 1)

  • Wrapped in an Option if named with prefix ? β†’ Method(?arg1 = Some 1)

☝ Other syntax for interop .NET: [<Optional; DefaultParameterValue(...)>] arg

Example:

☝ Notice the shadowing of parameters by variables of the same name let parity (* bool *) = defaultArg parity (* bool option *) Full

.NET optional parameters

There is another possibility to declare optional parameters, based on attributes. It's less handy but required for .NET interop or for using other attributes on parameters.

[<Optional; DefaultParameterValue(...)>] arg

The Optional and DefaultParameterValue attributes are available in open System.Runtime.InteropServices.

Example: tracing the call to a function by retrieving its name from the CallerMemberName attribute in System.Runtime.CompilerServices (*)

(*) Documentation πŸ”— : Caller information - F# | Microsoft Docs

Using |> pipe operator

You can use |> with a method with:

  • 1 parameter

  • 2 parameters, the last of which is .NET optional

πŸ’‘ If we want a 3rd parameter, we have to write a sub-lambda, like currying the function ourselves:

Parameter array

Allows specifying a variable number of parameters of the same type β†’ Via System.ParamArray attribute on the last method argument

πŸ’‘ Equivalent of Cβ™― public static T Max<T>(params T[] items)

Call Cβ™― method TryXxx()

❓ How to call in Fβ™― a Cβ™― method bool TryXxx(args, out T outputArg)? (Example: int.TryParse, IDictionnary::TryGetValue)

  • πŸ‘Ž Use Fβ™― equivalent of out outputArg but use mutation 😡

  • βœ… Do not specify outputArg argument

    • Change return type to tuple bool * T

    • outputArg becomes the 2nd element of this tuple

Call method Xxx(tuple)

❓ How do you call a method whose 1st parameter is itself a tuple?!

Let's try:

πŸ’‘ Explanation: TryGetValue(0,0) = method call in tuplified mode β†’ Specifies 2 parameters, 0 and 0. β†’ 0 is an int whereas we expect an int * int tuple!

Solutions

  1. πŸ˜• Backward pipe, but also confusing

    • friendsLocation.TryGetValue <| (0,0)

  2. πŸ‘Œ Double parentheses, but confusing syntax

    • friendsLocation.TryGetValue((0,0))

  3. βœ… Use a function rather than a method

    • friendsLocation |> Map.tryFind (0,0)

Method vs Function

Feature
Function
Curried method
Tuplified method

Partial application

βœ… yes

βœ… yes

❌ no

Named arguments

❌ no

❌ no

βœ… yes

Optional parameters

❌ no

❌ no

βœ… yes

Params array

❌ no

❌ no

βœ… yes

Overload

❌ no

❌ no

βœ… yes 1️⃣

Notes

1️⃣ If possible, prefer optional parameters to overloads.

2️⃣ Declaration order:

  • Methods generally don't need to follow the top-down compilation rule.

  • But it's required in the case of generic members β†’ See https://stackoverflow.com/q/66358718/8634147

  • We recommend declaring members from top to bottom, to ensure consistency with the rest of the code.

Method vs Function (2)

Feature
Function
Static method
Instance method

Naming

camelCase

PascalCase

PascalCase

Support for inline

βœ… yes

βœ… yes

βœ… yes

Recursive

βœ… if rec

βœ… yes

βœ… yes

Inference of x in

f x β†’ βœ… yes

K.M x β†’ βœ… yes

x.M() β†’ ❌ no

Can be passed as argument

βœ… yes : g f

βœ… yes : g T.M

❌ no : g x.M 1️⃣

1️⃣ Alternatives: β†’ Fβ™― 8: shorthand members β†’ g _.M() β†’ Wrap in lambda β†’ g (fun x -> x.M())

Static methods vs companion module

Companion module is more idiomatic β†’ default choice.

Static methods are interesting in some use cases:

  • Usage easier due to optional parameters 1️⃣

  • Usage more readable due to named arguments 3️⃣

  • Usage terser to instanciate record with several fields:

    • Depending on your use of Fantomas and its configuration, multi-line record expression can be verbose

    • A factory method call is usually formatted in a single line, hence terser. 2️⃣

    • When field labels are necessary for code clarity, we can use named arguments.

  • Record expressions can be ambiguous: we are not sure of which type it is.

    • A factory method can help resolve ambiguity: we force to use it qualified, hence the type is explicit.

Properties

≃ Syntactic sugar hiding a getter and/or a setter β†’ Allows the property to be used as if it were a field

There are 2 base ways to declare a property:

Getter

member this.Property = expression using this

☝ The expression is evaluated on each call.

Example:

member _.Property = expression involving side-effect

☝ This kind of property is generally not recommended. Prefer a method.

Automatic property

Automatic because a backing field is generated by the compiler.

Use case
Syntax
Equivalent in Cβ™―

Read-only

member val Property = value

public Type Property { get; }

Read/write

member val Property = value with get, set

public Type Property { get; set; }

☝ The property returns the same value on each call, mutation with the setter aside.

Example:

☝️ PersonName is immutable and, as a structπŸ“, has structural equality. It's the OO alternative to records.

Other cases

In other cases, the syntax is verbose: (details). πŸ‘‰ When possible, prefer methods as they are more explicit.

Properties and pattern matching

⚠️ Properties cannot be deconstructed β†’ Can only participate in pattern matching in when part

Indexed properties

Allows access by index, as if the class were an array: instance[index] β†’ Useful for an ordered collection, to hide the implementation

Set up by declaring the member Item

πŸ’‘ Property read-only (write-only) β†’ declare only the getter (setter)

☝ Notice the setter parameters are curried

Example :

Slice

Same as indexed property, but with multiple indexes

Declaration: GetSlice(?start, ?end) method (regular or extension)

Usage: .. operator

Operator overload

Operator overloaded possible at 2 levels:

  1. In a module, as a function let [inline] (operator-symbols) parameter-list = ...

    • πŸ‘‰ See session on functions

    • ☝ Limited: only 1 definition possible

  2. In a type, as a member static member (operator-symbols) (parameter-list) =

    • Same rules as for function form

    • πŸ‘ Multiple overloads possible (N types Γ— P overloads)

Example:

Last updated

Was this helpful?