Just in terms of implementing trees, I would do something like:

abstract Node;

type Leaf <: Node
    value::Float64;
end

type Branch <: Node
    left::Node;
    right::Node;
end
  


This should allow you to write methods that target either branch or leafs 
accordingly:

function sum(node::Branch)
    value::Float64 = 0;
    value += !isEmpty(node.left) ? sum(node.left) : 0;
    value += !isEmpty(node.right) ? sum(node.right) : 0;
end

function sum(node::Leaf)
    return node.value;
end




On Tuesday, May 13, 2014 11:47:14 PM UTC-4, Abram Demski wrote:
>
> Hi all,
>
> I'm implementing BSP trees, and I've run into a style issue which I'm 
> uncertain about.
>
> For my current purposes, the trees will be holding floats, but I wanted to 
> write things in a way which allowed other possibilities later. At first, I 
> made an explicit Leaf type:
>
> *Version 1*
>
>
> abstract Leaf
>
>
> type FloatLeaf <: Leaf
>
>         value::Float64
>
> end
>
>
> abstract Split # abstract type for space partition
>
>
> # starting out with axis-aligned splits, to implement arbitrary-angle later.
>
> # Axis-aligned splits make rectangles, hence RectSplit
>
> type RectSplit <: Split
>
>         var::Int
>
>         loc::Float64
>
> end
>
>
> # internal tree node. Two sides of a split are pos and neg.
>
> type TreeNode
>
>         split::Split # split definition
>
>         pos::Union(TreeNode, Leaf)  # negative side
>
>         neg::Union(TreeNode, Leaf)  # positive side
>
> end
>
>
> (This is actually a bit simplified; I also had a special Nothing type to 
> indicate an empty case. The NA type from dataframes was not appropriate 
> because I wanted different behavior.)
>
> I implemented functions on the trees mainly by using multi-dispatch in a 
> pattern-matching style, matching against TreeNode or Leaf types to 
> differentiate between the recursive case and the base case.
>
> I eventually decided that the Leaf type seemed rather pointless, because 
> it was just a container for a value. Using a Leaf type created some 
> annoyance with defining functions to operate on leaves, when the obvious 
> thing to do was just apply those functions to the leaf values. Also, I was 
> a little worried that putting stuff inside a Leaf container would be a 
> small efficiency hit (probably not much?).
>
> End result, I modified to this:
>
> *Version 2*
>
> abstract Split
>
>
> type RectSplit <: Split
>
>         var::Int
>
>         loc::Float64
>
> end
>
>
> type TreeNode{a}
>
>         split::Split # split definition
>
>         pos::Union(TreeNode{a}, a)  # negative side
>
>         neg::Union(TreeNode{a}, a)  # positive side
>
> end
>
>
> This is shorter, which seems like a good sign. It also manages to indicate 
> that all leafs should have the same type, which the previous version 
> didn't. However, I couldn't keep using multiple dispatch to define the 
> different cases for my functions. The closest thing I could do would be to 
> make the cases which match to Leaf type match to Float instead. This works 
> when the tree is only holding floats, but I wanted to handle more general 
> cases.
>
> Since this didn't seem right, I switched most of my code to use if-else in 
> one function rather than multiple dispatch on the types. Leaving the leaf 
> case for the final "else" allows me to write things in a generic way (no 
> explicit assumption that I'm dealing with Floats in functions that don't 
> need to interact with this assumption).
>
> However, this approach still doesn't work if I want to write a method 
> which applies to trees (be they internal nodes or leaf nodes) but allows 
> other function definitions for other types. Anything which isn't a TreeNode 
> is now assumed to be a leaf, the way I've done it. This isn't actually a 
> problem yet, but it could be at some point.
>
> Does this mean I was wrong to switch to the 2nd version?
>
> Any other suggestions for how this should go?
>
> Best,
>
> Abram
>  

Reply via email to