| Brian's profileInside F#BlogGuestbookNetwork | Help |
|
|
February 13 The basic syntax of F# - classes, interfaces, and membersToday's blog entry covers the F# syntax for authoring classes, interfaces, and members. Together with the previous blog entry, this probably covers the most common 90% or so of the language. Again today I will intentionally err on the side of simplicity, at the expense of total accuracy. Defining F# typesThere are lots of ways to define new types and type names in F#, and most involve declarations with the "type" keyword. Today I'll focus on just classes and interfaces; in a future blog entry I'll describe the syntax for the rest of the kinds of types (records, discriminated unions, enums, type abbreviations, units of measure, structs, delegates, exceptions, and modules). Classes (sans interfaces or inheritance)Here is an example skeleton class definition whose only purpose is to demonstrate most of the language syntax involved in defining classes: type public (*1*) MyClass<'a> public (*2*) (x, y) (*3*) as this (*4*) = static let PI = 3.14 static do printfn "static constructor" let mutable z = x + y do printfn "%s" (this.ToString()) (*5*) printfn "more constructor effects" internal new (a) = MyClass(a,a) static member StaticProp = PI static member StaticMethod a = a + 1 member internal self.Prop1 = x member self.Prop2 with get() = z and set(a) = z <- a member self.Method(a,b) = x + y + z + a + b I'll discuss each part in turn; the commented numbers are to help call out specific bits in the prose that follows. A class definition usually starts like this: type SomeClass(constructor-args) = ... but there are lots of optional bits that can go before the '=' when defining a class. Back in the main example, type public (*1*) MyClass<'a> public (*2*) (x, y) (*3*) as this (*4*) = the 'public' before (*1*) is the accessibility of the class type itself; new types default to being public. After the name of the class, if the class is generic, you specify the generic parameters in angle brackets (MyClass<'a> is a class with one generic parameter; the generic parameter is unused in this example, it just demonstrates the syntax). The next portion defines the so-called "implicit constructor" (whose meaning is described in more detail below). The 'public' before (*2*) is the accessibility of the implicit constructor (public is the default) and the stuff inside parentheses before (*3*) are the arguments to the implicit constructor. The "as this" before (*4*) is an optional way to name the current object (you can use any identifier you like, it is lexically scoped to all non-static portions of the class body); the only time you need it is when referring to "this" inside the implicit constructor body (such as on the line marked (*5*) in the main example). The body of the class conceptually has two portions, 'let's and 'do's (which run as part of construction), and 'member's. Both portions can have 'static' and instance (non-static) parts; non-static is the default. Names bound by 'let's are lexically scoped in the class body (and thus are always "private" to the class). The non-static 'let's and 'do's run as the body of the implicit constructor (.ctor), the static 'let's and 'do's comprise the type's static constructor (.cctor). In other words, this code static let PI = 3.14 static do printfn "static constructor" runs when something first refers to the enclosing MyClass type, and this code let mutable z = x + y do printfn "%s" (this.ToString()) (*5*) printfn "more constructor effects" runs whenever you create an instance of MyClass. (As with other .NET languages, it is rare to define a static constructor.) Members are the public interface to functionality in a class, and come in two main flavors: properties and methods. Members without arguments are "getter" properties; members with arguments are methods. From the example: static member StaticProp = PI static member StaticMethod a = a + 1 member internal self.Prop1 = x member self.Prop2 with get() = z and set(a) = z <- a member self.Method(a,b) = x + y + z + a + b Members can be static or not (non-static is the default). Non-static (instance) members must declare a self-identifier. Just as with the optional "as this" from the class declaration, you can use any identifier you like; the identifier is bound to the current object and scoped to the member's body (in these examples I chose "self"). Members default to being public (though "Prop1" demonstrates that you can add an optional accessibility specifier). If you want to define a property with a "setter" as well as a "getter", you can use the syntax as in "Prop2". To define constructors overloads other than the implicit constructor, use a syntax like this: internal new (a) = MyClass(a,a) The accessibility specifier is optional (defaults to public, just like other members). The "body" of the constructor must "call forward" to another constructor (eventually ending up with a call to the implicit constructor). Pragmatically, this means your implicit constructor must initialize everything, and thus probably takes the most arguments of any constructor overload. There are a number of features of members I am omitting for simplicity, including named and optional parameters, overloading, events, and fields. You can read the language spec for more details on these features, or ask questions as a blog comment. InterfacesDefining and using interfaces in F# is very straightforward. An interface is a type that only defines abstract members and/or inherits other interfaces. Here are a couple examples: type IFooable = abstract member Foo : int -> int type IQuxable = abstract member Qux : unit -> string interface IFooable "IFooable" is an interface with a method "Foo" that takes an int and returns an int. "IQuxable" is an interface that inherits "IFooable" and adds another method named "Qux". To have a class implement an interface, just add an 'interface...with' declaration to the end of the class body for each interface you want to implement: type FooQux() = member this.SomeMethod() = printfn "hi" interface IQuxable with member this.Foo x = x + 1 member this.Qux() = "qux!" Here "FooQux" is a class containing a method "SomeMethod" and implementing the "IQuxable" interface. Note that in F#, when a class implements an interface, the interface implementation is always explicit, which means you need an explicit upcast to call interface methods. For example let fooQux = new FooQux() fooQux.SomeMethod() let x = (fooQux :> IFooable).Foo 42 // must upcast to call "Foo" Abstract classes and "virtual" methodsHere is an example of an abstract class: [<AbstractClass>] type SomeBase() = member this.ConcreteMethod y = y + 1 abstract member AbstractMethod : int -> int abstract member VirtualMethod : int -> int default this.VirtualMethod x = x + 1 An abstract class is like a (normal, concrete) class, except it contains some abstract (not implemented) members that a subclass must implement. What would be a "virtual" method in C# is expressed as an 'abstract' method that has a 'default' implementation. An abstract class type must be marked with the "AbstractClass" attribute. InheritanceWith inheritance come three more F# keywords: 'inherit', 'override', and 'base': type SomeDerived() = inherit SomeBase() override this.AbstractMethod z = z + 1 override this.VirtualMethod x = 1 + base.VirtualMethod x member this.OtherMethod() = () The 'inherit' clause defines the base class of a class, and also calls the base constructor as part of the implicit constructor. (That is, in this example the zero-argument implicit constructor of "SomeDerived" calls the zero-argument constructor of "SomeBase".) Abstract members that have no implementation must be overridden by the derived class (or else the derived class can also be marked abstract). Abstract methods with default implementations can be overridden; to call the inherited method, use the 'base' keyword. Other miscellanyI have intentionally omitted many details to keep this blog entry reasonably short (but still cover all the major common bits of syntax). Here are a few notable omissions:
Comments (9)
Brian McNamara
has turned off comments on this page.
TrackbacksWeblogs that reference this entry
|
|
|