With multiple dispatch I am able to specialize any function already written to a type, but when it comes time to define my own “interfaces” (although here they are just generic functions) I get a bit confused.
I want to translate this idea to Julia:
Nestable<B, L> {
branch(n:Nestable<B, L>): Nestable<B, L>[]
construct(value: B, branches: Nestable<B, L>[]): Nestable<B, L>[]
attach(n:Nestable<B,L>, branches: Nestable<B,L>[]): Nestable<B,L>[]
value(n:Nestable<B, L>): B
isLeaf(n:L | Nestable<B,L>): n is L
}
This type defines a generic set of functions that work with anything that can be nested.
The naive approach gives me:
# i'm not sure what value this has when I can't use the type params
function isleaf(n::Any)::Bool end
function value(n::Any)::Any end
function attach(n::T, branches::T)::T where T end
function construct(value::Any, branches::T)::T where T end
# AbstractArray might even be too specific, Set, Map etc are all fine. Any iterable will do.
function branch(n::T)::AbstractArray{T} end
I tried to look for a Iterable<T>
idea in Julia to get some inspiration but noticed that instead Julia chooses to use functions to define behavior, not types. This is cool and seems powerful but I’m not sure how to apply it to my use case.
Any thoughts? Here’s one of the Nestable
related functions I’m trying to port over:
typescript
// this is a concrete example implementation for arrays but i would want to be more general
export type Branch<B, L> = [B, ...(L | Branch<B, L>)[]]
// Ideally I'd be able to say Tree<Branch<B, L>> = L | Branch<B, L>
export type Tree<B, L> = L | Branch<B, L>
export type Forest<B, L> = Tree<B, L>[]
/**
Very generic tree transform.
Takes 3 functions and a branch and
returns a new branch where the values are mapped,
the leaves are transformed, and the nodes themselves are also transformed.
By default branches generated by transforming leaves are transformed by `n`.
If you don't want this, then pass `false` for the `deep` parameter.
*/
function transform<B, L, B2, L2>(
l: (t: L) => Tree<B2, L2>[],
b: (b: B) => B2,
n: (t: Branch<B2, L2>) => Forest<B2, L2>[],
tree: Branch<B, L>,
deep: boolean = true): Branch<B2, L2> {
return construct(b(value(tree)), branch(tree) |> R.chain(
(x: Tree<B, L>) => isLeaf(x)
? l(x) |> (deep ? R.chain((y: Tree<B2, L2>) => isLeaf(y) ? [y] : n(y)) : identity)
: n(transform(l, b, n, x))
));
}
// identity = x -> x
// chain composes functions together