piaste
piaste

Reputation: 2058

Idiomatic way to declare static and instance member at once?

When I extend a type with a new function, I usually want it to be available from both dot-notation and free form. Either can be more readable depending on the situation, and the former helps with IntelliSense while the latter helps with currying.

In C#/VB.net, extension methods do this (although I cannot restrict the function to a static method of the extended static class, as in F#). I can write the function once and then invoke it both ways:

<Extension>
public function bounded(s as string, min as UShort, max as UShort) as string
    if min > max then throw new ArgumentOutOfRangeException
    if string.IsNullOrEmpty(s) then return new string(" ", min)
    if s.Length < min then return s.PadRight(min, " ")
    if s.Length > max then return s.Substring(0, max)
    return s
end function

' usage     
dim b1 = bounded("foo", 10, 15)
dim b2 = "foo".bounded(0, 2)

(That's not quite perfect yet, as I'd like bounded to be a static method of String, but C#/VB.Net can't do that. Point to F# in that regard.)

In F#, on the other side, I have to declare the function separatedly from the method:

// works fine
[<AutoOpen>]
module Utilities = 
    type List<'T> with
            member this.tryHead = if this.IsEmpty then None else Some this.Head        
    module List =             
        let tryHead (l : List<'T>)  = l.tryHead

Question: Is there a more elegant way to declare both methods at once?

I tried to use:

// doesn't quite work
type List<'T> with        
    member this.tryHead = if this.IsEmpty then None else Some this.Head
    static member tryHead(l : List<'T>) = l.tryHead

which at least would let me skip the module declaration, but while the definition compiles, it doesn't quite work - someList.tryHead is OK, but List.tryHead someList results in a Property tryHead is not static error.

Bonus question: As you can see, the static member definition requires a type annotation. However, no other type could have access to the method that was just defined. Why, then, can't the type be inferred?

Upvotes: 3

Views: 131

Answers (1)

Mark Seemann
Mark Seemann

Reputation: 233135

I don't know of a way to declare both APIs in a single line of code, but you can get rid of the type annotations by making the function the implementation, and then defining the method it terms of the function:

[<AutoOpen>]
module Utilities = 
    module List =
        let tryHead l = if List.isEmpty l then None else Some (List.head l)
    type List<'a> with
        member this.tryHead = List.tryHead this

Upvotes: 3

Related Questions