Is Julia lazy?

Methods that accept an integer will also accept Z{P}, so you may gain access to that functionality. This depends on that you have correctly implemented the basic functionality (or ‘interface’) of Integer, otherwise it may end throwing errors or doing strange things. My code examples probably has not done that.

You cannot use that functionality for Z{P}, or you’ll have to implement it yourself. Off the top of my head I can’t remember what functionality that would be, but basically any method that someone has written with ::Integer in its type signature.

Then the pool of available methods that work ‘out of the box’ for your method is a bit smaller.

That won’t work. You cannot subtype a concrete type, only abstract types.

2 Likes

It’s a bit unfortunate that the interfaces to base types (Integer, Number, etc) aren’t documented better. In lieu of that the documentation of the array interface is a decent conceptual substitute. If you have a type that you want to behave like an array you can subtype abstractarray, and implement the few functions listed in the documentation and your new type will get all the functions already defined for abstractarrays across the ecosystem.

The same is true of Integer, Number, etc. but the required functions are not as well documented.

8 Likes

thank you it is perfectly clear. Possibly another question this evening. My wife wants to bring me back down to this earth.

1 Like

Indeed. While I was typing up that Z{P, T} code, I was constantly running into weird effects, such as

>> length(Z13(0):Z13(12))
0

and collect returned an empty vector, even though I could iterate over it and print all the elements.

It felt a bit like playing whack-a-mole. New problems kept popping up when I tried new things, then I tried, half-blindly, to patch up the necessary functionality, but ended up creating ambiguity problems because I didn’t know which methods to add, and some of the time I chose the wrong ones.

A bit frustrating, and I have no idea if I’m even close, what to add, what to remove, and when the next error will pop up.

1 Like

I understand that Julia’s abstract types are the equivalent of C++ abstract classes or Java interfaces. Don’t know anything equivalent in Python’s world.

The best bet would probably be to find a package that implements a new type subtyping the same abstract type you wish to subtype and see what they implement. For example if you wanted to create a new <: Real, looking at the Rational type in base would be good as presumably it implements all needed functions. There are surely packages out there that subtype integer already to crib off of.

1 Like

Good point. The implementation grew a bit ‘organically’, so the realization that this should be done more systematically came a bit late.

Another question is: what is the right abstract type, Integer or Unsigned, or Real? It sure seems like an integer type, though.

1 Like

Wow. I just realized that basing the Z{P, T} type on UInt was a bad idea! Wraparound effects when subtracting are plain wrong. Use Int (or Int8 etc. instead!!)

jl> zu1, zu2 = Z{13, UInt8}.((2, 5))
(2₁₃, 5₁₃)

jl> zu1 - zu2
6₁₃   # Wrong-o!!

jl> zs1, zs2 = Z{13, Int8}.((2, 5))
(2₁₃, 5₁₃)

jl> zs1 - zs2
10₁₃  # correct!
1 Like

Good thing we’re still prototyping! :slight_smile:

1 Like

Have you tried looking at how an existing package defines new number types?

For example, GaloisFields.jl implements finite fields. This package implements a lot of features, but the basic number type for prime fields is implemented here.

Another nice and relatively simple example of a custom number type can be found in the Quaternions.jl package, which defines a quaternion type here.

6 Likes

Good evening ! (night is coming in my native France).
Let’s continue, if you agree with my continuing questions. I will play the ‘Candide’ role or ‘advocate of the devil’ according the situation. It’s just a matter of style to stimulate the debate.
OK as I understood you derive (sub-type) from some abstract type to access functionality.
But in any case you’re obliged to explain Julia how to add, multiply, subtract F_p objects, just like me who didn’t use any sub-typing.
We do the same things parallel, don’ t we ?
You :
`Base.:+(a::Z{P}, b::Z{P}) where {P} = Z{P}(a.x + b.x)
Base.:-(a::Z{P}, b::Z{P}) where {P} = Z{P}(a.x - b.x)
Base.:-(a::Z{P}) where {P} = Z{P}(-a.x)
Base.:*(a::Z{P}, b::Z{P}) where {P} = Z{P}(a.x * b.x)type or paste code here
me :

Base.:+(k::F_p,h::F_p) = F_p(k.x+h.x)
Base.:*(k::F_p,h::F_p) = F_p(k.x*h.x)
Base.:-(k::F_p)=F_p(-k.x)
Base.:-(k::F_p,h::F_p)=F_p(k.x-h.x)

So I ask again : what advantage do you draw from sub-typing ???
Tell me what you can do which I cannot according our two approaches.

For sure I’ll have a look at this.
For now I have no ambitions except teaching the base of linear algebra using simple programs as examples (python and now julia //)
But I just published a site of algebra specially dedicated to mobile devices (telephones and tablets). Sorry but everything is in French.
For the moment the algebra site has a development mainly guided by historical considerations. As a consequence complete results concern mostly the characteristic 0 where separability is obvious.
Nevertheless it’s my intention to develop more the general theory and to give examples with appropriate programs.
So I will need models for Fp polynomials, Fp-vector spaces, finite extension of Fp and so on to make computations.
For sure this module will be of great help.
This is a plan for future now I’m busy with learning correct use of sub-typing, parametric types and so on.

There are functions which are defined for Integer directly and therefore also for all its suptypes.
For example, when you take @DNF s simpler example without the subtyping, you couldn’t do

a = Z{5}(3) 
a ^ 2 
1 Like

you mean that once * overloaded not necessary to overload exponent ^.

Yes, you have to define a limited number of methods, like basic arithmetic. But every other method that anyone ever defines for Integer, which could be any countless number, are now available, as well as all the functionality that derives from those again.

2 Likes

I think this is the source of a lot of your confusion here. Julia has nothing at all equivalent to C++'s abstract classes. Python classes are similar to C++'s (except python people would typically add in an old school raise NotImplemented for any methods not defined rather than actually having it abstract. See class - Is it possible to make abstract classes in Python? - Stack Overflow Java Interfaces are a little bit closer, but even then I don’t think it is the right way to think about it.

The closest thing in C++ are trait classes. Remember that Julia is much closer to the parametric/generic programming in C++ than anything object orietned. Look at code which uses overloading of functions defined on templated classes in C++ to get into the right spirit. With C++ overloading and templates, you can have things “dispatch” statically to different functions, but it is tough to carefully control which functions are called.

So when doing generic programming in C++ you sometimes subclass from a empty classes (these are not abstract in the sense of having a non-implemented member function). These are just used to organization of traits for dispatching. But don’t be fooled as the use of a class and inheritance from them is just a trick, and has nothing to do with classes in object-oriented programming. This has some of the tricks in this insanity: http://cppedinburgh.uk/slides/201603-tmp.pdf Also Substitution failure is not an error - Wikipedia explains some of this stuff

With that C++ lets you rig up what you could call “multiple static dispatch” for functions when all of the types are known at compiletime - although it is often incredibly painful to get there.


OK, now forget all of that if you didn’t know it. C++ is powerful but insane due to people having to find hacks to implement these features on an old language. In Julia you can do dynamic dispatch at runtime and with different specializations for all of the parameters of the function (i.e. multiple dispatch). The abstract types are your way to use existing code for the implementations of your functions if they fit into the same “interface” in a loose generic programming sense.

I wrote up some lecture notes on some of these topics: 5. Introduction to Types and Generic Programming — Quantitative Economics with Julia and 6. Generic Programming — Quantitative Economics with Julia

12 Likes

“C++ is powerful but insane” +1

6 Likes

Yes, I tried with Integer sub-typing a^n is evaluated correctly for n integer >0 without any extra-code except overloading of *.
But a^0 and a^-1 lead (logically) to errors.
Can you find one or two examples let’s say more ‘spectacular’ because honestly I’m not too much impressed by this one. In a field you must be able to compute any power positive or negative of any non null element. To do this you have to overload iszero and inv and define a ^k so to redefine on the way the positive powers isn’t much job.
Examples given by DNF are more convincing But it is the explicit ‘promotion rule’ which does the job.
Well, access to some functionality is not straightforward from derivation.

OK forget about this, I mean benefit of sub-typing now. As I answered to Fliks it seems to me modest without the associated promotion rule.
I made some test with my simple and naive definition of Z_p with a global p (which you hate). I adapted
your rule to my context

Base.promote_rule(::Type{<:Number}, ::Type{F_p}) = F_p

and after this I could multiply a F_p by a natural

So to this point Julia knows how to multiply two F_p, how to multiply two naturals and by promotion a natural by a F_p 12a and a*12 are understood and give the same result.
I tried to make some changes . Actually your rule converts any Type sub-typing the very general Number abstract type to F_p. But in reality it is just necessary to convert integers. So I changed :<Number for :<Integer it works. What is the reason of your choice (I mean Number and not Integer) ?
As I understood it is only possible from a type to a sub-type, not conversely. One cannot decide to identify a F_p with it’s x member, or is it possible and how to do it.
This is pure curiosity I don’t see any practical interest to do it now, I just want to close the chapter of explicit promotion-conversion rules.

You mean in this particular case? I don’t have an overview of that, it depends on what you want to do. If you have no interest in functionality beyond basic arithmetic, then you don’t need subtyping. I found that printing of arrays is nicer when you subtype Integer, but that’s just my preference.

Sure. But why not use both?

I’m sure it works, I just don’t think students should learn to use globals like that. And it’s less elegant, and less readable and instructive. Type parameters seem to map so cleanly to the parameterization of Zp, it seems like a good case for demonstrating both.

The reason is arbitrary and not properly thought-out. I wanted 2.0 * z and so on to work.

I get

jl> z = Z13(5)
5₁₃

jl> zz = Z13.(rand(0:12, 3, 3))
3×3 Matrix{Z13}:
 3₁₃   5₁₃   4₁₃
 1₁₃  12₁₃   8₁₃
 8₁₃   0₁₃  11₁₃

jl> z^0
1₁₃

jl> zz^0
3×3 Matrix{Z13}:
 1₁₃  0₁₃  0₁₃
 0₁₃  1₁₃  0₁₃
 0₁₃  0₁₃  1₁₃

Division or inversion don’t work:

jl> z^(-1)
ERROR: MethodError: no method matching AbstractFloat(::Z13)

You’ll have to teach Julia about the rules of division/inversion in modular arithmetic, or let it convert to floats.