Larry OBrien
Larry OBrien

Reputation: 8606

Idiomatic F# for "only once" post-initialization mutation?

In the following, the way I define Animator is common in OOP systems, where it is considered fine to mutate this object's state:

type MyViewController() = 
    inherit UIViewController() 

    //Is there a better way than this?
    member val Animator = null with get, set 

    override this.ViewDidLoad() = 
        base.ViewDidLoad()

        //this.View is defined in base class and is only valid *now*
        this.Animator <- new UIDynamicAnimator(this.View) 

Is there a more idiomatic way to define Animator that communicates: "null->object is ok, object -> object is forbidden"?

(Obviously, I could write a custom set function that checks and throws at runtime, but that seems more annoyingly "clever" than helpful.)

Upvotes: 2

Views: 145

Answers (2)

Tomas Petricek
Tomas Petricek

Reputation: 243051

Once the library depends so heavily on inheritance, it is really hard to avoid the usual OO patterns, even when you're using F#. So to be fair, I would probably write exactly the same code you did and just live with the fact that this part of the code is not going to be that pretty (and use more functional approach in the parts of the application where you're not forced into OO style by the framework).

Also, in this case, I'd just live with the null - you can use option, but you're not getting any benefits, because you cannot do much if the value is None anyway.

Presumably, in your full source code, you are overriding other methods of the class and you are creating Animator so that it can be used in the other methods. One thing you could do is to extract the code from the class and write something like this:

type IInitializedView = 
    abstract OtherMethod : UIViewController -> unit

type MyViewController(viewInitialized:UIView -> IInitializedView) = 
    inherit UIViewController()     
    let mutable initializedView = None

    override this.ViewDidLoad() = 
        base.ViewDidLoad()
        initializedView := Some(viewInitialized this.View)

    override this.OtherMethod() = 
        viewInitialized |> Option.iter (fun vi ->
            vi.OtherMethod() )

The idea here is that MyViewController calls your function when it has view and your function then creates a new interface that handles the other method - but your interface is created only after everything is properly initialized!

let vc = MyViewController(fun view ->
  // Now we have valid 'view' so we construct animator!
  let animator = UIDynamicAnimator(view)
  { new IInitializedView with
      member x.OtherMethod() = 
        // no problem here, because we have animator... 
        animator.Whatever() })

But I don't know enough about Xamarin to say whether this would work OK in a full more complex system.

Upvotes: 4

kaefer
kaefer

Reputation: 5741

I think what @Carsten gets at is that you shouldn't introduce null values unless you are absolutely forced to by interop with other CLI languages. Types declared in F# wouldn't allow them anyway, unless decorated with an AllowNullLiteralAttribute.

The value wrapped in the Option<'a> type would be a somewhat direct translation, but still has mutability. I'm assuming that you do not really need the property setter, replacing the automatic property with a let-bound value.

type MyViewController() = 
    inherit UIViewController() 

    let mutable animator = Option<UIDynamicAnimator>.None
    member x.Animator = animator.Value

    override this.ViewDidLoad() =
        base.ViewDidLoad()
        animator <- Some <| new UIDynamicAnimator(this.View)

On the other hand, wrapping in Lazy<'a> is a little bit more in the functional spirit.

type MyViewController() as this = 
    inherit UIViewController() 

    let animator = lazy(new UIDynamicAnimator(this.View))
    member x.Animator = animator.Value

    override this.ViewDidLoad() =
        base.ViewDidLoad()
        // Optional; only if you need the object creation right now
        animator.Force() |> ignore

Upvotes: 3

Related Questions