Quantcast
Channel: haxe – Stroep
Viewing all articles
Browse latest Browse all 11

Biwise operations made easy with Haxe

$
0
0

haxeThis blogpost is about making bitwise operations in Haxe more easy. If you like doing micro optimization in your code, you probably want to use bitwise operations. But I found the main problem about bitwise operations is that they are hard to read. If you know the syntax and know what it does it might be “doable”, but if you work in a team it might get harder if not everyone is familiar with the syntax.

A common example of using bitwise operators is to put multiple states into one integer. Some call them flags.

For example, lets say you want to build a app where users have different rights to view a document. You could create a User class with multiple booleans like this:

class User{
    public var name:String;
    // user rights
    public var CAN_VIEW:Bool = true;
    public var CAN_ACCESS:Bool = true;
    public var CAN_EDIT:Bool = false;
    public var CAN_GRANT_ACCESS:Bool = false;
    public var CAN_REVOKE_ACCESS:Bool = false;
    public var CAN_DELETE:Bool = true;
}
 

But if you want to optimize it, you could also put all those values into an variable. Computers deal with ones and zeros right?

class User{
    public var name:String;
    // user rights
    public var rights:String = "000001";
}
 

In this case we create a string with zeros and ones. We could say that the last value means “can view” and the one next to it “can access” etc.. In our case only the last digit is “1” so that means it is on (true). the others are “0” so they are false.

Understanding bitwise operations

Well, this could work, but working with numbers in strings is not very elegant, and also not practical. And it’s awkward when you want to add another flag to it. Why don’t we just use a number for that? That’s where bitwise operations come in. Let’s put it in an integer:

class User{
    public var name:String;
    // user rights
    public var rights:Int = 0;
}
 

A bit on counting binary

Given the last explanations,
we can say:

CAN_VIEW   == 000001
CAN_ACCESS == 000010

That would mean that:
CAN_ACCESS and CAN_VIEW == 000011

When we translate it to code it would be:
1 | 2 == 3

For those who can count binary, 000011 in real value equals 3. By the way, the leading zero’s don’t mean anything.

Ok, let’s move on and define all our flags. Define a new class that has all the access flags in it like this:

class Access {
    public static inline var CAN_VIEW:Int          = 1;
    public static inline var CAN_ACCESS:Int        = 2;
    public static inline var CAN_EDIT:Int          = 4;
    public static inline var CAN_GRANT_ACCESS:Int  = 8;
    public static inline var CAN_REVOKE_ACCESS:Int = 16;
    public static inline var CAN_DELETE:Int        = 32;
}

As you see the order is: 1, 2, 4, 8, 16, 32 (and if we go further 64, 128, 256, 512 etc etc..)
This means when 1 | 2 == 3, we can now do:

var user = new User();
user.rights = Access.CAN_VIEW | Access.CAN_ACCESS;
trace(user.rights); // 3

Play with this live-example to see how you can blend different flags.

Nice! now we know how to mix and blend different rights to the user. Let’s say you want to make somebody admin you can do this:

user.rights = Access.CAN_VIEW | Access.CAN_ACCESS | Access.CAN_EDIT | Access.CAN_GRANT_ACCESS | Access.CAN_REVOKE_ACCESS | Access.CAN_DELETE;
 

To go back on how we declared the Access class, I think it can be improved. It now has that count that is slightly annoying since it doesn’t feel natural. Let’s look at this table.

real value binary left shifted
1 000001 1 << 0
2 000010 1 << 1
4 000100 1 << 2
8 001000 1 << 3
16 010000 1 << 4
32 100000 1 << 5

As you notice in the last column, the same values can be achieved with a left shift <<. So “000001” equals “1” but is the same when we write it like “1 << 0”. Personally I think its more readable to have this last column in your code because it just increases normally, what do you think?

Nicer syntax with enum abstract

Let’s use the power of Haxe to simplify the Access class. Instead of using static vars, let’s create an enum abstract.

@:enum abstract Access(Int) from Int to Int
{
    var CAN_VIEW          = 1;
    var CAN_ACCESS        = 2;
    var CAN_EDIT          = 4;
    var CAN_GRANT_ACCESS  = 8;
    var CAN_REVOKE_ACCESS = 16;
    var CAN_DELETE        = 32;
}
 

No wait, let’s also use the left shift so we don’t have to count those 1,2,4,8 etc..

@:enum abstract Access(Int) from Int to Int
{
    var CAN_VIEW           = 1 << 0;
    var CAN_ACCESS         = 1 << 1;
    var CAN_EDIT           = 1 << 2;
    var CAN_GRANT_ACCESS   = 1 << 3;
    var CAN_REVOKE_ACCESS  = 1 << 4;
    var CAN_DELETE         = 1 << 5;
}
 

That looks a bit friendlier because if you now want to add another field, you can just add “1 << 6” and “1 << 7“. But hey this post was about readability right? Why not create a simple function so we can get rid of this “1 << ” thingy? Yep, we can! 😀

@:enum abstract Access(Int) from Int to Int
{
    var CAN_VIEW          = value(0);
    var CAN_ACCESS        = value(1);
    var CAN_EDIT          = value(2);
    var CAN_GRANT_ACCESS  = value(3);
    var CAN_REVOKE_ACCESS = value(4);
    var CAN_DELETE        = value(5);

    static inline function value(index:Int) return 1 << index;
}
 

Ah, in my opinion everybody can maintain this. Even if you don’t understand what’s happening. Since we use the ‘inline’ there is no actual function call in the output. You can see in this live-example how the generated output looks like. Neat! Now, that’s it for the Access class.

We created an actual type!

Hey! Since Access is now an actual type, we can even define user.right as follows:

public var rights:Access; // isn’t this nice!?
 

But it still translates and works as an integer.

Working with bitsets

Let’s go back to the user.rights field. What if we want to remove a flag? The syntax for that would be:

user.rights = user.rights & ~Access.CAN_EDIT;

😯 Aiiii, there goes the readability isn’t it?
Well lets create a utility class that contains a remove function. I use this BitSets class from Flambe, which has some other nice functions too (like testing if a integer contains a flag).

class BitSets
{
    inline public static function remove (bits:Int, mask:Int):Int
    {
        return bits & ~mask;
    }

    inline public static function add(bits:Int, mask:Int):Int
    {
        return bits | mask;
    }

    inline public static function contains (bits :Int, mask :Int) :Bool
    {
        return bits & mask != 0;
    }
}
 

We can now do this much more readable code:

user.rights = BitSets.remove(user.rights, Access.CAN_EDIT);

But let take it one more step further, and use the Haxe static extensions feature. The key to use it is by adding the keyword ‘using’ instead of ‘import’ on top of your class.

Then we can do just this:

// add flags
user.rights = user.rights.add(Access.CAN_EDIT).add(Access.CAN_ACCESS);

// remove flags
user.rights = user.rights.remove(Access.CAN_EDIT);

// check if flag exist
if (user.rights.contains(Access.CAN_ACCESS))
{
    trace("can access");
}
 

With Haxe 3.2 you can leave out the enum class (if it’s imported or a class inside your module), so we can write:

// add flags
user.rights = user.rights.add(CAN_EDIT).add(CAN_ACCESS);

// remove flags
user.rights = user.rights.remove(CAN_EDIT);

// check if flag exist
if (user.rights.contains(CAN_ACCESS))
{
    trace("can access");
}
 

It doesn’t get cleaner than that.
Check out this full live-example to see how it all comes together and what the effect is on the generated output.

Conclusion

  • We saved ourselves from multiple booleans on a user class, we’ve learned how to use flags.
  • We made a enum abstract type called Access for our user.rights property.
  • Working with bitwise operators can be readable.
  • This BitSets class is very nice to have.
  • The functions created to make this readability happen does not affect the output thanks to inlining.
  • You can use this technique for quite some classes. I think when you see classes with multiple booleans, you could consider using flags.

Related posts


Viewing all articles
Browse latest Browse all 11

Trending Articles