# Abstract type hierarchies along with OOP constructed types

**URL:** <https://discourse.julialang.org/t/abstract-type-hierarchies-along-with-oop-constructed-types/52100>\
**Category:** General Usage\
**Tags:** question, package, base\
**Created:** [December 19, 2020, 6:26pm UTC](https://discourse.julialang.org/t/abstract-type-hierarchies-along-with-oop-constructed-types/52100 "2020-12-19T18:26:18Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![emmettgb](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/emmettgb/32/20393_2.png) [@emmettgb](https://discourse.julialang.org/u/emmettgb)\
**Post date:** [December 19, 2020, 6:26pm UTC](https://discourse.julialang.org/t/abstract-type-hierarchies-along-with-oop-constructed-types/52100/1 "2020-12-19T18:26:18Z")

</div>

Hi guys, I am new to Julia’s Discourse, but rather familiar with Julia. I maintain a package called Lathe.jl which is an object-oriented machine-learning package in Julia with syntax similar to SkLearn. As such, for an example my syntax would usually go like this, this is the ordinal encoder, as an example:

```julia
function OrdinalEncoder(array)
    uni = Set(array)
    lookup = Dict()
    [push!(lookup, (value => i)) for (i, value) in enumerate(uni)]
    predict(arr) = [row = lookup[row] for row in arr]
    ()->(predict;lookup)
end

```

The issue I am running into is that with these unsafe boxed types. I am able to have sklearn-like syntax, and it is faster than dispatch by around half, and I get an initialization function. The only problem is that these types are compound creations and very abstract on definition. They are of the type var(#1515, #1314), etc. Not really the most traceable types. My intention would be to combine this with modern Julian functional programming and create a supertype hierarchy:

```julia
abstract type h end
function hello(test)
    () -> (test) <: h 
end

```

That actually ran, but then running that type under T \<: h, I get a return of false. Of course this means it also doesn’t work with dispatch. The only significant way to get around this would be to put this type into a struct and call everything as children of both of the types. Obviously, I don’t want to do that…  
Thanks for the help, obviously this a unique issue. I was thinking maybe there could be a way I could wrap it directly as children of that struct maybe, probably through dispatching a direct import of \<:. Let me know what you guys think!

---

<div class="post-metadata">

**Author:** ![mauro3](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mauro3/32/292_2.png) [@mauro3](https://discourse.julialang.org/u/mauro3)\
**Post date:** [December 19, 2020, 6:52pm UTC](https://discourse.julialang.org/t/abstract-type-hierarchies-along-with-oop-constructed-types/52100/2 "2020-12-19T18:52:00Z")

</div>

Are you maybe running into [performance of captured variables in closures · Issue #15276 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/issues/15276)? Your boxes could indicate that. There are some potential tricks to work around that issue. Otherwise, I don’t understand your problem, so I cannot help there.

---

<div class="post-metadata">

**Author:** ![FPGro](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fpgro/32/20822_2.png) [@FPGro](https://discourse.julialang.org/u/FPGro)\
**Post date:** [December 20, 2020, 1:12am UTC](https://discourse.julialang.org/t/abstract-type-hierarchies-along-with-oop-constructed-types/52100/3 "2020-12-20T01:12:40Z")

</div>

> [@emmettgb](#):
>
> My intention would be to combine this with modern Julian functional programming and create a supertype hierarchy:
> 
> ```julia
> abstract type h end
> function hello(test)
> () -> (test) <: h 
> end
> 
> ```
> 
> That actually ran, but then running that type under T \<: h, I get a return of false. Of course this means it also doesn’t work with dispatch.

I’m sorry, but it is not entirely clear to me what your goals are here. Maybe you could try to reword what your expected outcome and behavior is and we’ll certainly find a way.

Anonymous functions can obviously not be subtypes of a newly defines abstract type, but is this really necessary?  
If you just need or want the object.method() syntax to work, you can easily define new methods for `getproperty` which is implicitly called by `struct.field`.  
You may be abled to wrap your returns into appropriate types while maintaining the apparent behavior that you now have, but it’d be nice to have a clearer example to judge that.

> [@emmettgb](#):
>
> probably through dispatching a direct import of \<:.

IIRC `<:` is one of the rare build-in functions that you can not create new methods for.

---

<div class="post-metadata">

**Author:** ![Vasily\_Pisarev](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vasily_pisarev/32/7929_2.png) [@Vasily\_Pisarev](https://discourse.julialang.org/u/Vasily_Pisarev)\
**Post date:** [December 20, 2020, 6:28pm UTC](https://discourse.julialang.org/t/abstract-type-hierarchies-along-with-oop-constructed-types/52100/4 "2020-12-20T18:28:35Z")

</div>

Hello and welcome!

Could you please give more concrete examples of what it is you aim to achieve and also what you mean by “faster than dispatch”?  
If you want “instance methods” in Julia, you can easily do that by having a struct with function fields, e.g.:

```julia
struct OrdinalEncoder{V,D,P}
    predict::P
    lookup::D
    function OrdinalEncoder(array)
        lookup = Dict(v => i for (i,v) in array |> unique |> enumerate)
        predict(arr) = map(x->lookup[x], arr)
        V, D, P = keytype(lookup), typeof(lookup), typeof(predict)
        return new{V,D,P}(predict, lookup)
    end
end

```

(keep in mind that it is not what function fields are usually used for).

---

<div class="post-metadata">

**Author:** ![emmettgb](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/emmettgb/32/20393_2.png) [@emmettgb](https://discourse.julialang.org/u/emmettgb)\
**Post date:** [December 20, 2020, 11:33pm UTC](https://discourse.julialang.org/t/abstract-type-hierarchies-along-with-oop-constructed-types/52100/5 "2020-12-20T23:33:14Z")

</div>

Actually, your approach should be optimal. I am referring to how using these methods as children instead of dispatching types for example with the ordinal encoder:  
encoder = OrdinalEncoder(y)  
encoded\_data = OrdinalEncoder.predict(y)  
Compared to  
struct encoder  
Etc…  
end

predict(encoder)  
But the code you presented could be helpful for compilation, as well as what I need, but I will have to test it. I will reply back!

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [December 21, 2020, 12:54am UTC](https://discourse.julialang.org/t/abstract-type-hierarchies-along-with-oop-constructed-types/52100/6 "2020-12-21T00:54:05Z")

</div>

> [@emmettgb](#):
>
> ```julia
> function OrdinalEncoder(array)
> uni = Set(array)
> lookup = Dict()
> [push!(lookup, (value => i)) for (i, value) in enumerate(uni)]
> predict(arr) = [row = lookup[row] for row in arr]
> ()->(predict;lookup)
> end
> 
> ```
> 
> The issue I am running into is that with these unsafe boxed types. I am able to have sklearn-like syntax, and it is faster than dispatch by around half, and I get an initialization function. The only problem is that these types are compound creations and very abstract on definition. They are of the type var(#1515, #1314), etc. Not really the most traceable types. My intention would be to combine this with modern Julian functional programming and create a supertype hierarchy:

If you want your stuff to be legible, you should use real structs.

Furthermore, it’s not boxing that’s causing performance problems, but things like the useless creation of a `Set`, the un-necessary allocation of an array, and the creation of a `Dict{Any, Any}` which could be made concrete.

I’d write your `OrdinalEncoder` like this (if I was deadset on using this hack for OOP):

```julia
function OrdinalEncoder(array)
    lookup = Dict{eltype(array), Int}()
    for (i, value) ∈ enumerate(array)
        if value ∉ values(lookup)
            lookup[value] = i
        end
    end  
    predict(arr) = [row = lookup[row] for row in arr]
    ()->(predict;lookup)
end

```

Now,

```julia
julia> OrdinalEncoder([:a, :b, :a, :c]).lookup
Dict{Symbol, Int64} with 3 entries:
  :a => 3
  :b => 2
  :c => 4

julia> OrdinalEncoder([:a, :b, :a, :c]).predict([:b, :c])
2-element Vector{Int64}:
 2
 4

julia> OrdinalEncoder([:a, :b, :a, :c]) |> typeof
var"#7#10"{var"#predict#8"{Dict{Symbol, Int64}}, Dict{Symbol, Int64}}

```

This should be significantly more efficient. But I would still just use a `struct` rather than closures here. If you want, you can look into [GitHub - rjplevin/Classes.jl: A simple, Julian approach to inheritance of structure and methods](https://github.com/rjplevin/Classes.jl) for something more familiar to you.
