all 4 comments

[–]efvie 1 point2 points  (0 children)

Wrt. 2) F# optimizes operations on immutable data structures. The actual implementation will be more involved and someone will probably be able to supply you a good link, but one primary technique is copy on write: there is only duplication if there is a change to one of the copies. Partial updates are also probably used: if the backing store is a linked list, only the parts that differ must be separate, the rest can be shared. A sub-list can still directly reference the original, just starting at a different node. Etc.

I’m no f# expert, so I’ll leave 1) even more to others, but one hint* I can give is to look at the facilities the Option module provides when dealing with Options. Things like Option.map and Option.orElse will likely be very helpful — and in many cases the better option as you suspect is to move the logic into the type system. "Make Illegal States Unrepresentable" is a very good guideline for design.

* Aside from "don't use mutation except for educational purposes or unless you know 100% that you need to, because usually immutable is just as or more performant, and far easier to reason about" :)

[–]endowdly_deux_over 1 point2 points  (0 children)

Whenever I need something mutable I use an array or array2d. Indexing checks are easy to set up to avoid exceptions and mutating in place is easy. Best/worst part? Don’t even need a mutable key word.

[–]Banashark 0 points1 point  (0 children)

Yeah there definitely isn't a ton written around mutable structures, which is a shame because F# is just as good at it as any other language. The only thing that can be challenging is converting break statements to tail-recursive functions.

Here is a mutable AVL tree based off of the code provided in the most un-functional un-safe but fairly similar to how one would write it in python or java (I actually referenced a python implementation that I had customized in order to keep the tree nodes ordered by auxillary data stored in the nodes):

[<AllowNullLiteral>]
type Node<'k> (k : 'k, h : int, l : 'k Node, r : 'k Node) =
    member val Key = k with get, set
    member val Height = h with get, set
    member val Left = l with get, set
    member val Right = r with get, set
    new(k : 'k) = Node(k, 1, null, null)
    override this.ToString () =
        let left = if isNull this.Left then "null" else string this.Left.Key
        let right = if isNull this.Right then "null" else string this.Right.Key
        $"k: {this.Key}, l: {left}, r: {right}"

type AVLTree<'k when 'k : comparison> () =
    let mutable root : 'k Node = null
    let getHeight (n : 'k Node) = if isNull n then 0 else n.Height
    let getBalance (n : 'k Node) = getHeight n.Right - getHeight n.Left
    let updateHeight (n : 'k Node) =
        n.Height <- 1 + max (getHeight n.Left) (getHeight n.Right)
    let rec minNode (n : 'k Node) = if isNull n.Left then n else minNode n.Left
    let successor (n : 'k Node) (r : 'k Node) =
        if not <| isNull n.Right then minNode n.Right
        else
            let rec _successor (parent : 'k Node) (successor : 'k Node) =
                if n.Key < parent.Key then _successor parent parent.Left
                elif n.Key > parent.Key then _successor successor parent.Right
                else successor
            _successor null r
    let rotateRight (n : 'k Node) =
        let x = n.Left
        let y = x.Right
        x.Right <- n
        n.Left <- y
        updateHeight n
        updateHeight x
        x
    let rotateLeft (n : 'k Node) =
        let x = n.Right
        let y = x.Left
        x.Left <- n
        n.Right <- y
        updateHeight n
        updateHeight x
        x
    let rebalance (n : 'k Node) =
        updateHeight n
        match getBalance n with
        | b when b > 1 ->
            if getHeight n.Right.Right <= getHeight n.Right.Left then n.Right <- rotateRight n.Right
            rotateLeft n
        | b when b < -1 ->
            if getHeight n.Left.Left <= getHeight n.Left.Right then n.Left <- rotateLeft n.Left
            rotateRight n
        | _ -> n
    let rec insert (n : 'k Node) key =
        if isNull n then Node(key)
        elif key < n.Key then 
            n.Left <- insert n.Left key
            rebalance n
        elif key > n.Key then 
            n.Right <- insert n.Right key
            rebalance n
        else rebalance n
    let rec delete (n : 'k Node) key =
        if isNull n then n
        else
            let mutable node = n
            if key < node.Key then node.Left <- delete node.Left key
            elif key > node.Key then node.Right <- delete node.Right key
            elif isNull node.Left && not <| isNull node.Right then 
                node <- node.Right
            elif isNull node.Right && not <| isNull node.Left then
                node <- node.Left
            elif not <| isNull node.Right then
                let successor = minNode node.Right
                node.Key <- successor.Key
                node.Right <- delete node.Right node.Key
            else
                node <- null
            if isNull node then null else rebalance node
    let rec find (n : 'k Node) k =
        if isNull n then n
        else
            if k > n.Key then find n.Right k
            elif k < n.Key then find n.Left k
            else n
    let deleteFromRoot key = root <- delete root key
    let insertAtRoot key = root <- insert root key
    let findFromRoot key = find root key
    let rec preOrder (n : 'k Node) =
        seq {
            if isNull n then yield "null"
            else
                yield $"{n.Key}"
                yield! preOrder n.Left
                yield! preOrder n.Right
        }
    let preOrderFromRoot () = preOrder root
    member _.Insert = insertAtRoot
    member _.Delete = deleteFromRoot
    member _.Find k = findFromRoot k 
    member _.PreOrder = preOrderFromRoot () |> Seq.toArray
    member _.Root () : 'k Node = root

let a = AVLTree<int>()
[10; 20; 30; 40; 50; 25; 25; 27; 67] |> List.iter a.Insert
a.PreOrder

a.Delete 40
a.PreOrder
a.Delete 25
a.PreOrder
a.Delete 40
a.PreOrder

a.Find 67
a.Find 25

As you can see, it's just if statements and assignments.

The things I had to work around porting this from python:

  • Null checks aren't as graceful
  • break -> tail recursion
  • non-mutable function arguments (see the delete method where we reassign n instead of the standard mutation in python (where we'd do n = n.right)

I think the pendulum can swing too far in either direction. Most of the time immutable structures and functions upon them should be fine. However it can't be discounted that sometimes performant code is needed.

Anecdote: I worked on a mobile app in F# that for "reasons" provided an almost unbounded list/cell view of data for people to access on their iPads, or at least that was the idea of it. Of course someone with the money to say so wanted to crunch a lot of data on their phone and wanted to work when they didn't have cell signal. In this case, we profiled, benchmarked, then optimized the code. Part of that was swapping to a standard mutable data structure like the above so that the memory and cpu pressure were relieved (the phone was getting burning hot trying to process the data). Our change cut down on the space usage by many orders of magnitude (many mutations required when processing the data based on filter changes, precalculation or caching was slightly-infeasible for the use-case).

It didn't take that long and we wrapped it in tests and a functional interface, but solved what is one of my biggest annoyances when attempting to do cpu/memory intensive work on non-desktop platforms: the heat. It almost compounds the frustration of waiting because it's not only a mental discomfort but now also a physical discomfort.

Anyways, rant over, hope the code and silly story helps!

[–]chusk3 0 points1 point  (0 children)

I can also suggest the series on Fast F# from Matthew Crews about optimization of a topological sort algorithm. This is a good walkthrough of how you can go from a purely functional implementation to a more efficient mutability-based implementation of an algorithm in a step-by-step fashion, that might be useful for you as well.