Methods (F#)

A method is a function that is associated with a type. In object-oriented programming, methods are used to expose and implement the functionality and behavior of objects and types.

// Instance method definition.
[ attributes ]
member [inline] self-identifier.method-name parameter-list [ : return-type ]=
    method-body

// Static method definition.
[ attributes ]
static member [inline] method-name parameter-list [ : return-type ]=
    method-body

// Abstract method declaration or virtual dispatch slot.
[ attributes ]
abstract member self-identifier.method-name : type-signature

// Virtual method declaration and default implementation.
[ attributes ]
abstract member [inline] self-identifier.method-name : type-signature
[ attributes ]
default member [inline] self-identifier.method-name parameter-list[ : return-type ] =
    method-body

// Override of inherited virtual method.
[ attributes ]
override member [inline] self-identifier.method-name parameter-list [ : return-type ]=
    method-body

Remarks

In the previous syntax, you can see the various forms of method declarations and definitions. In longer method bodies, a line break follows the equal sign (=), and the whole method body is indented.

Attributes can be applied to any method declaration. They precede the syntax for a method definition and are usually listed on a separate line. For more information, see Attributes (F#).

Methods can be marked inline. For information about inline, see Inline Functions (F#).

Non-inline methods can be used recursively within the type; there is no need to explicitly use the rec keyword.

Instance Methods

Instance methods are declared with the member keyword and a self-identifier, followed by a period (.) and the method name and parameters. As is the case for let bindings, the parameter-list can be a pattern. Typically, you enclose method parameters in parentheses in a tuple form, which is the way methods appear in F# when they are created in other .NET Framework languages. However, the curried form (parameters separated by spaces) is also common, and other patterns are supported also.

The following example illustrates the definition and use of a non-abstract instance method.

type SomeType(factor0: int) =
   let factor = factor0
   member this.SomeMethod(a, b, c) =
      (a + b + c) * factor

   member this.SomeOtherMethod(a, b, c) =
      this.SomeMethod(a, b, c) * factor

Within instance methods, do not use the self identifier to access fields defined by using let bindings. Use the self identifier when accessing other members and properties.

Static Methods

The keyword static is used to specify that a method can be called without an instance and is not associated with an object instance. Otherwise, methods are instance methods.

The example in the next section shows fields declared with the let keyword, property members declared with the member keyword, and a static method declared with the static keyword.

The following example illustrates the definition and use of static methods. Assume that these method definitions are in the SomeType class in the previous section.

static member SomeStaticMethod(a, b, c) =
   (a + b + c)

static member SomeOtherStaticMethod(a, b, c) =
   SomeType.SomeStaticMethod(a, b, c) * 100

Abstract and Virtual Methods

The keyword abstract indicates that a method has a virtual dispatch slot and might not have a definition in the class. A virtual dispatch slot is an entry in an internally maintained table of functions that is used at run time to look up virtual function calls in an object-oriented type. The virtual dispatch mechanism is the mechanism that implements polymorphism, an important feature of object-oriented programming. A class that has at least one abstract method without a definition is an abstract class, which means that no instances can be created of that class. For more information about abstract classes, see Abstract Classes (F#).

Abstract method declarations do not include a method body. Instead, the name of the method is followed by a colon (:) and a type signature for the method. The type signature of a method is the same as that shown by IntelliSense when you pause the mouse pointer over a method name in the Visual Studio Code Editor, except without parameter names. Type signatures are also displayed by the interpreter, fsi.exe, when you are working interactively. The type signature of a method is formed by listing out the types of the parameters, followed by the return type, with appropriate separator symbols. Curried parameters are separated by -> and tuple parameters are separated by *. The return value is always separated from the arguments by a -> symbol. Parentheses can be used to group complex parameters, such as when a function type is a parameter, or to indicate when a tuple is treated as a single parameter rather than as two parameters.

You can also give abstract methods default definitions by adding the definition to the class and using the default keyword, as shown in the syntax block in this topic. An abstract method that has a definition in the same class is equivalent to a virtual method in other .NET Framework languages. Whether or not a definition exists, the abstract keyword creates a new dispatch slot in the virtual function table for the class.

Regardless of whether a base class implements its abstract methods, derived classes can provide implementations of abstract methods. To implement an abstract method in a derived class, define a method that has the same name and signature in the derived class, except use the override or default keyword, and provide the method body. The keywords override and default mean exactly the same thing. Use override if the new method overrides a base class implementation; use default when you create an implementation in the same class as the original abstract declaration. Do not use the abstract keyword on the method that implements the method that was declared abstract in the base class.

The following example illustrates an abstract method Rotate that has a default implementation, the equivalent of a .NET Framework virtual method.

type Ellipse(a0 : float, b0 : float, theta0 : float) =
    let mutable axis1 = a0
    let mutable axis2 = b0
    let mutable rotAngle = theta0
    abstract member Rotate: float -> unit
    default this.Rotate(delta : float) = rotAngle <- rotAngle + delta

The following example illustrates a derived class that overrides a base class method. In this case, the override changes the behavior so that the method does nothing.

type Circle(radius : float) =
    inherit Ellipse(radius, radius, 0.0)
     // Circles are invariant to rotation, so do nothing. 
    override this.Rotate(_) = ()

Overloaded Methods

Overloaded methods are methods that have identical names in a given type but that have different arguments. In F#, optional arguments are usually used instead of overloaded methods. However, overloaded methods are permitted in the language, provided that the arguments are in tuple form, not curried form.

Example: Properties and Methods

The following example contains a type that has examples of fields, private functions, properties, and a static method.

type RectangleXY(x1 : float, y1: float, x2: float, y2: float) =
    // Field definitions. 
    let height = y2 - y1
    let width = x2 - x1
    let area = height * width
    // Private functions. 
    static let maxFloat (x: float) (y: float) =
      if x >= y then x else y
    static let minFloat (x: float) (y: float) =
      if x <= y then x else y
    // Properties. 
    // Here, "this" is used as the self identifier,
    // but it can be any identifier. 
    member this.X1 = x1
    member this.Y1 = y1
    member this.X2 = x2
    member this.Y2 = y2
    // A static method. 
    static member intersection(rect1 : RectangleXY, rect2 : RectangleXY) =
       let x1 = maxFloat rect1.X1 rect2.X1
       let y1 = maxFloat rect1.Y1 rect2.Y1
       let x2 = minFloat rect1.X2 rect2.X2
       let y2 = minFloat rect1.Y2 rect2.Y2
       let result : RectangleXY option =
         if ( x2 > x1 && y2 > y1) then
           Some (RectangleXY(x1, y1, x2, y2))
         else
           None
       result

// Test code. 
let testIntersection =
    let r1 = RectangleXY(10.0, 10.0, 20.0, 20.0)
    let r2 = RectangleXY(15.0, 15.0, 25.0, 25.0)
    let r3 : RectangleXY option = RectangleXY.intersection(r1, r2)
    match r3 with
    | Some(r3) -> printfn "Intersection rectangle: %f %f %f %f" r3.X1 r3.Y1 r3.X2 r3.Y2
    | None -> printfn "No intersection found."

testIntersection

See Also

Other Resources

Members (F#)