Suppose that I want to define a new concrete type P (a probability). I want variables of type P to behave like floats except that e.g. passing a value of 1.1 to a function with argument of type P or assigning a value of 1.1 to an element array of elements of type P produces an error.
I have read the documentation on types, constructors, and methods but don’t know what the simplest way is of accomplishing this. Any hints?
@dpsanders I know I can do that but then I have to type p.x * y if I want multiplication or define all sorts of extra functions for it to otherwise behave like a float.
Avoid primitive types since seems like you dont need them as the manual’s warning suggests.
The simplest way is defining a simple type:
struct Probability #<: Real eventually you can declare it as a subtype of Real if it respects Real number's algebra
value::Float64
end
Then you define all the functions for manipulation that you need, for example multiplication: Base.:(*)(p1::Probability,p2::Probability) = Probability(p1.value * p2.value)
You can check out one of those packages implementing special floating point types like DoubleFloats.jl for a list of function you might want to define.
Then maybe promotion rules and conversions if needed.
For errors on array element assignment you can assert it when you define setindex!.
Passing a value of 1.1 (a Float64) to a function with argument of type P is already an error if the function does not accept Float64.
It sounds like to me you want a float that throws an exception when ever it’s not a valid probability. I’m guessing you want it to be in the range of 0.0 <= x <= 1.0. As other’s have said that’s going to be a structure with something like:
struct Probability
value::Float64
Probability(v) = begin
v < 0.0 && throw("Negative probability")
v > 1.0 && throw("Probably greater than 1.0")
return new(v)
end
end
Then you would need to define all the functions you want to use with this new type, i.e. +,*, -, etc. I don’t think even a primitive will save you much work because you would have to define all those functions anyway.
I’m guessing what you really want, is to define a type that is really a float and have the compiler generate code to test that it’s value is inside a specific range. I don’t believe there is a way to hook the compiler like that.
Also note that probabilities aren’t just floats restricted to [0,1]. What is p + q or p - q supposed to mean (if you don’t know anything about the meaning of the events P and Q)?
What I want is for the new type to behave exactly as a float except that one checks the bounds on assignment without having to think of all situations in which I might want to use it and define functions accordingly.
Thanks @carstenbauer . I want it to behave just like a float except on assignment (in which case bounds are to be checked): the probabilities were just an example.
Julia doesn’t have a mechanism that allows you to define exactly one invariant enforcing constructor and then get to act like you had the original type and delegate everything (except the constructor) to an existing type. There are approximations, as others have noted, but nothing that makes creating a refinement type trivial.
But @carstenbauer’s point is very important to keep in mind: a new type that enforces invariants is likely not what you want. For example, you likely want adding probabilities to produce probabilities – but that means you can’t just rely on the existing methods for addition…
Thanks @pixel27 . I know I can do that. What I want is for the new type to behave exactly as a float except that one checks the bounds on assignment without having to think of all situations in which I might want to use it and define functions accordingly.
What I was hoping for is some type of automatic promotion to a float via promote_rule or some such function.