Hard aliasing for types


#1

Hello there,

Is it possible to hard alias primitive types? For example,
I would like to have type MyNumber, on which I will do dispatch for custom arithmetic operators.
The obvious solution is

struct MyNumber n::Number end

Suppose I will do heavy calculations with this type. Is it the most efficient way?
Or is something like
"alias MyNumber = Number"
possible, where MyNumber becomes a distinct type.
The “const MyNumber = Number” obviously does not work like that.


#2

You are conflating two concepts: aliasing and wrapping.

Alias a type as

const Type1 = Type2

From then on, they will be synonyms, ie Type1 === Type2. Consequently, methods that work for one will work for the other (because it is the same).

Wrap a type as

struct MyWrapper{T}
    value::T 
end

The type parameter is necessary for performance.

You will need to define the relevant functions, forwarding them to the value and possibly rewrapping. For some types (eg numbers), there are libraries that make this easier (just be more specific on what you want).

AFAIK there is no solution which would avoid the need to define the methods yet make the types distinct.


#3

#4

Yes I know my solution is not aliasing, hence I asked
if there is another way.

I never intended to have untyped wrapper.

To be more specific, I am writing an algebra system for Julia.
Since I have to capture expressions for delayed computations and/or transformations
I defined several types such as

struct MySymbol
      s::Symbol
end

and so on.

What you are suggesting is basically

type MyWrapper{T}
       value::T 
end

const MySymbol = MyWrapper{Symbol} 

but I fail to see any difference.
The performance issues comes when dealing with untyped fields, which
is not the case here.


#5

Thanks Kris,

Nonetheless I need more than numbers. I have my own Symbol, Expr, Number, and String
which have to be captured for doing algebra.

Anyway, it is not like it is a lot of hassle, just a couple of additional lines.


#6

But it was, in your original example (Number is an abstract type).

If you are defining several wrapper types with similar but occasionally different behavior, consider defining a single wrapper type and specializing on parameter types for behavior that is different. Whether this simplifies your code is of course something only you can tell.


#7

x-ref: https://github.com/JuliaLang/julia/issues/9821


#8

Maybe I misunderstand something.

So there is a difference between

MyWrapper{Number}

and my original code?

As I understand that doc, it says we should avoid abstract types
when we can specialize. But in my case, I can not do that.
I need a “new” Number type, and obviously can not specialize.


#9

I thought you meant the type Base.Number, while now it is apparent that you were using it as a placeholder for a type name.


#10

It’s a little hard to tell exactly what you want, but if the field inside your type is always a Symbol, then it’s true that you don’t need a parameterized type; you can just do:

struct MySymbol
  x::Symbol 
end

and if you want your type to be dispatched like a Number, you can do:

struct MySymbol <: Number
  x::Symbol 
end

#11

Okay, to be more specific here is the code I went with:

"An abstract symbolic expression"
abstract type ASymbolic end;

type AWrapper{T} <: ASymbolic
    val::T
end

# Need our own dispatch on types
const ANumber = AWrapper{Number}
const AVar = AWrapper{Symbol}
const AExpr = AWrapper{Expr}

"Result of symbolic computation"
const AResult = Union{ANumber, AVar, AExpr}

It is part of computer algebra system which is trying to leverage full of julia, instead
of just being a wrapper to some other library.

Thanks for your replies.


#12

const ANumber = AWrapper{Number}

This will perform poorly because Number is an abstract type: https://docs.julialang.org/en/stable/manual/performance-tips/#Avoid-fields-with-abstract-type-1

You could instead do:

julia> const ANumber = AWrapper{T} where T <: Number

julia> AWrapper{Int} <: ANumber
true

julia> AWrapper{Float64} <: ANumber
true

#13

Okay, then I might just deal with two numeric types in symbolic expressions as well. Somehow I thought
it would complicate the code more, but it is really just a matter of defining another type and doing some additional testing.

For example, when detecting expression of the form

coef1*x + coef2*x

The new test for factorization would just need to look for types

if typeof(coef1) <: Number 
 ...

Thanks to wonderful macros
a lot of operators are generated automatically anyway.


#14

As a suggestion, if you want to try to leverage all of Julia, I think something like Reduce is the way to go.

For example, if you want to calculate partial Normal moments

julia> using Reduce, Compat, SpecialFunctions
Reduce (Free PSL version, revision 4218), 22-Sep-2017 ...

julia> Reduce.Rational(false);

julia> @generated function normal_moment(x, ::Val{N} = Val{0}()) where N
           ex = int(:( x^$N * exp(-x^2/2 ) ), :x)
           root_2π = sqrt(2π)
           :( $ex / $root_2π )
       end

julia> normal_moment(0)
0.0

julia> normal_moment(0.5)
0.19146246127401312

julia> normal_moment(0.5, Val(2))
0.015429797891863349

julia> normal_moment(1.5, Val(2))
0.23891640523230437

julia> normal_moment(10, Val(4)) ###Pick a big number to get the kurtosis of a standard normal.
1.5000000000000002

This could be useful if you wanted to integrate Hermite polynomials.

If you like statistics, you can also have a lot of fun with moment generating functions:

julia> @generated function gamma_moment(a, b, ::Val{N} = Val{0}()) where N
           ex = :((1 - t/b)^-a)
           for i ∈ 1:N
               ex = df( ex, :t )
           end
           quote
               t = 0
               $ex
           end
       end
gamma_moment (generic function with 2 methods)

julia> gamma_moment(2, 2, Val(1)) ##The mean
1.0

julia> gamma_var(α, β) = gamma_moment(α, β, Val(2)) - gamma_moment(α, β, Val(1))
1.5

julia> gamma_var(2, 2)
0.5

julia> gamma_var(3.4, 1.7)
1.1764705882352953

julia> gamma_var(4, 2)
1.0

Be warned though that if the Val you’re using isn’t known at compile time, the code wont be type stable.


#15

Thanks Elrod, this is helpful.

I will definitely have a look at this package. Just by superficially looking, it is unclear to me
if this package can be used to achieve, ex. quickly implementing rules for doing computations ex. in a Fock space, or any other type of symbolic tasks.
Also it seems it is just an interface to some external program, while I want it to be 100% julia for maximum hackability.

I looked through the code, and the approach is pretty much the same I want to take.
I also parse expression using julia AST capabilities and so on.