New concrete number type

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?

Do you want a new primitive type: Types · The Julia Language

Or do you want a one-element struct that wraps Float64 but has a constructor that does invariant enforcement?

1 Like

A primitive type (if I can do enforcement).

Make a struct that wraps a float and put the test in an inner constructor for the struct.

1 Like

@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.

Perhaps I should have a look at how Bool is defined.

Don’t you have to do that anyways? I would assume that it’s even worse if you create your own primitive type.

4 Likes

There are various packages for forwarding methods to new types.

1 Like

No. I just had a look at the definition of a Bool in https://github.com/JuliaLang/julia/blob/master/base/bool.jl Hopefully something along those lines will work.

any recommendations?

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.

1 Like

For example, there is the @forward macro in Lazy.jl: Lazy.jl/macros.jl at master · MikeInnes/Lazy.jl · GitHub

3 Likes

I don’t understand. Bool isn’t even defined in the file you linked. Also it contains lots of method definitions for Bool (making my point?).

1 Like

Also, be careful at using the type system only to catch errors, such approach has a cost in compilation time and development time.

1 Like

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.

1 Like

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)?

1 Like

Thanks @smldis . 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.

I’ll check out the code for DoubleFloats.

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…

2 Likes

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.

1 Like