I told my 13-year old girl that Julia has both rational and irrational types. How we started this conversation is a different story
As I showed off how it works in the REPL, she pointed out that it’s wrong to have sqrt(2) not being an irrational type… common sense tells us that a number is either rational or irrational but cannot be neither.
I understand that a value can only has one type and it makes perfect sense to put it under AbstractFloat. However, does this behavior bother anyone? Should rational/irrational really be a characteristic of a number a type rather than a type, hence we just have various supporting functions around them?
julia> sqrt(2) isa Irrational
false
julia> sqrt(2) isa Rational
false
julia> sqrt(2) isa Real
true
julia> sqrt(2) isa AbstractFloat
true
sqrt is not equivalent to the mathematical definition of \sqrt{}, it is an approximation of the latter in the relevant subset of numbers (in the case of sqrt(::Int), the set of Float64). Most computer languages (except for CAS) work this way.
Irrational is a nice addition to the standard floating point menagerie, for representing irrational values “exactly”, for some operations. See mathconstants.jl for details.
AFAIK Irrational represents a computable number, in the original sense: It is a piece of code that can compute successive approximations (including a-posteriori bounds). Now, arithmetic on computable numbers is nice in math but kinda bad in practice (equality is undecidable!). Hence, sqrt(2) is an eagerly evaluated approximation in julia.
I would not argue against a rename of Irrational to LazyComputableReal (which eagerly approximates on arithmetic), though (or did I misunderstand the intention behind Irrational?).
Arithmetic in programming languages is typically implemented using Floating-point arithmetic - Wikipedia. Assuming that this carries over 1-to-1 to ideal mathematical structures will not end well.
In this specific case, the type Irrationals purpose is simply to lift an irrational number (which can’t be represented in the standard numerical types) to the type domain where it can be dispatched on.
I think it is possible that you might have. AFAICT Irrational is not lazily computed at all, eg π is just 3.14159265358979323846. I think that their advantage is that certain operations are exact, eg \log(e) = 1, see here.
It’s much more clever than that – it’s an object that really does represent the true irrational and “knows its value adaptively” relative to its local context. E.g.
I think to talk about whether a Float64 number to be rational or not is meaningless, so the current behaviour is ok. Float64 is just an approximation of real number with some precision restriction. Different real numbers can have same Float64 number corresponding to it. Mathematically, you can use both rational/irrational number to approximate both rational/irrational number with whatever precision you want, so it is useless to find out whether the approximation is rational or irrational.
I’d say the confusing fact is that e.g. 1 isa Rational is false. Indeed, contrary to what one might expect, Rational is not the abstract supertype of all kinds of rationals, but a concrete type representing rationals in one specific way. To correspond more closely to math terminology, that type would have to be called Fraction or something like that.
Julia is not a formal mathematical language, I realized that it falls far short from that when I realized I can’t define my own operator characters. Julia is a programming language, it does not carry a 1-1 correspondence to mathematical vernacular, although it gets very close to it. What I realized though is that Julia will never be able to used as a formal mathematical vernacular due to limitations in programming language design, pure mathematics is much more general, abstract, and has far more complicated context sensitive grammar than any useful programming language could attain. But it is neat that Julia is able to come closer than others, but I think languages like agda or some theorem provers get even closer than julia is.
You cannot add new ones to the parser, but I think that most every Unicode character that could be used as an operator can be defined as one.
I counted at least 552 operators available in v0.7, with different precedence levels.
For example:
julia> ⊗(a,b) = println("operate on $a and $b")
⊗ (generic function with 1 method)
julia> 123 ⊗ 456
operate on 123 and 456
Yes, I’m aware, I’ve added a character to that list already, but was not able to get all the characters I wanted to use approved, since there is some context-sensitive ambiguity with how some symbols should be interpreted.
Also, the order of operations is entirely pre-set in the list of characters, so if I wanted to use an operator with a different order of operations precedence, I can’t do it, since the Julia developers want to ensure compatibility for all users. However, some languages like agda are more flexible in that regard.
For a Mathematician, this is a limitation, since we like to define operations and symbols on a whim, many symbols can have completely different interpretations and order of operations in different contexts, for a pure mathematician at least, so Julia is not able to handle that situation.
Very cool. This is a good opportunity to teach about differences between theory and its instantiations on real computers. sqrt(2) isa Irrational being false tells you something about what sqrt(2) actually does — it’s returning a rational approximation to the true value, since we decided that would be more useful. So that answer is “correct” in the highly literal way that computers work.
Another fun example is Float64(pi) < pi, which is true.
It would be a great exercise to implement an irrational type representing square roots, if you think she might be interested in that.
But I just realized that this doesn’t work on symbols yet (it works for Expr objects), so log(:e) doesn’t work yet, but that should be a simple fix to implement.