Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Idiomatic constructors in F#

Tags:

inline

f#

I started with a small OpenGL wrapper and I run into a small problem. I usally create my data with records like this

type Shader = 
    { Handle : int }
    static member Create filepath stype = 
        let h = loadshader filepath stype
        { Handle = h }

type VertexShader = 
    { shader : Shader }
    static member Create path = 
        { shader = Shader.Create path ShaderType.VertexShader }

type FragmentShader = 
    { shader : Shader }
    static member Create path = 
        { shader = Shader.Create path ShaderType.FragmentShader }

I am always using a static constructor with a non tupled function. But now I want another type where I would like to have optional parameters.

like

type VAO = 
    { Handle : int }

    static member Create vboList ?ibo =
     ........

The problem is that this doesn't seem to be possible with non-tupled functions and I don't want to mix my Create methods with tupled and non-tupled functions.

I wonder if I even wrote idiomatic F# code in the first place. How would you approach this "problem"?

like image 994
Maik Klein Avatar asked Feb 02 '14 17:02

Maik Klein


1 Answers

You're right -- if you want to define optional parameters for a method, you need to define the arguments in tupled form. One way you could approach this would be to move the implementation of your Create method into a private method (and rename it, e.g., to CreateImpl), then re-define Create as an overloaded method which will simply dispatch to the implementation. For example:

type VAO =
    { Handle : int }

    static member private CreateImpl (vboList, ibo : _ option) =
        failwith "Not implemented."

    static member Create vboList =
        VAO.CreateImpl (vboList, None)

    static member Create (vboList, ibo) =
        VAO.CreateImpl (vboList, Some ibo)

Another approach, is to define a module (it needs to be placed below the definition of your VAO type) which defines curried functions which "wrap" the static VBO.Create(...) method. This'll work however you decide to declare the arguments for Create (i.e., using a single method with tuple-style argument declaration and optional parameters, or using the overloading approach described above). For example, if you don't use my overloading method, and decide to just define Create as static member Create (vboList, ?ibo):

// The [<CompilationRepresentation>] attribute is necessary here, because it allows us to
// define a module which has the same name as our type without upsetting the compiler.
// It does this by suffixing "Module" to the type name when the assembly is compiled.
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix>]
module VAO =
    let create (vboList : _ list) =
        VAO.Create vboList

    let createWithIndex vboList ibo =
        VAO.Create (vboList, ibo)

Those module functions are very simple, so it's very likely they'll be inlined automatically by the F# compiler and you won't pay any run-time costs for them. If you compile in Release mode, inspect the assembly's IL and find that's not the case, then you can add the inline keyword to the function definitions to force them to be inlined. (Best not to do force inlining unless you benchmark and are certain it offers a real performance gain.)

One tip, unrelated to your question about the optional arguments -- consider re-defining at least some of your types as structs rather than F# records. Record types are compiled into classes (i.e., reference types) so you pay the additional indirection cost for no reason; defining them as structs will eliminate this indirection and give your application better performance. Better yet, if you're able to, re-use one of the existing OpenGL wrapper libraries like OpenTK or OpenGL4Net.

like image 60
Jack P. Avatar answered Nov 15 '22 07:11

Jack P.