µObjects: Fluent Types

I expect us all to be aware of the anti-pattern Primitive Obsession. If not - there are some quick resources available.

Now that we have an idea of Primitive Obsession; I'll give my reason(s) for not using primitives.

The core of Primitive Obsession is to not represent a concept with a data type that isn't explicitly that concept. A zip code (in the USA) is NOT a number. It's digits, but not a number.
A phone number - Not a number, digits and sometimes other characters.
A social security number - NOT a number.
A password - NOT a string.

These things rarely matter until they do. If you're lucky, you don't have to burn the weekend. Money is always the big one that bites people in the ass.
Money is NOT a decimal. Money is money; a phone number is a phone number... Represent them as the concept in the code.

This is why primitives are a smell - WHAT is the primitive? I have no way of knowing without extensive code traversal. If I get a IPhoneNumber object; I know exactly what it is. I have exactly what I can do with it available.


That's the basics.
I don't want to use ANY primitives.
No string. Not even for strings. No int for numbers. I don't want these floating about the code because I can't control the behavior.
We don't have any associated meaning to the primitive (almost typed 'object' there). We don't know what it actually is, or what we actually SHOULD be doing.
I can do any manipulations with a string. I can do any math with a number. Valid for what it represents or not; I can.

Having even basic values as primitives prevents us from constraining the behavior available. We're operating without an interface, We have no contract of behavior to know what is permissible. When will the madness end?!?!


I've found it pretty easy to create classes for the operations that I want.

    public class And : Bool
    {
        private readonly Bool _boolA;
        private readonly Bool _boolB;

        public And(Bool boolA, Bool boolB)
        {
            _boolA = boolA;
            _boolB = boolB;
        }

        protected override bool RawValue() => _boolA && _boolB;
    }

the Or : Bool looks very similar.
The base is looking like:

    public abstract class Bool
    {
        public static readonly Bool True = new BoolOf(true);
        public static readonly Bool False = new BoolOf(false);

        public static implicit operator bool(Bool origin) => origin.RawValue();
        public Bool Not() => new Not(this);
        public Bool And(Bool and) => new And(this, and);
        public Bool Or(Bool or) => new Or(this, or);

        protected abstract bool RawValue();
    }

WHY?! There's a lot of, "But WHY?!" to this.

Delayed Execution

None of the calculations to compute will be done until the value is needed. Any heavy lifting is done later, not when building the expression.

Readbility

This style builds on itself for the evaluation chain. You don't have a confusing varA && varB ^ varC || varD ^ varE && varF where someone {looks around innocently} gets really clever trying to find a minimum boolean statement.
With fluent types it's varA.And(varB).Xor(varC).Or(varD).Xor(varE).And(varF). I'm going to say that the fluent is more clear about the order of operation.
I use XOR (^) so infrequently (probably also don't need it) that I have no idea if it has higher precendence than && or ||. I'd have to go check. I've encountered engineers that don't know what ^ is. Is this the ordering you WANT? or will you need to add ()s to enhance readability?
What's the order of operation with the fluent. varA is Andd with varB which is then Xord with varc which is then... It enacts the operations in the declared order. That is generally what we expect.
I checked the docs for ^ - higher precedence than &&. I didn't expect that.
Here's the minimum? paran'd version to have the declared order of operations
(varA && varB) ^ varC || (varD ^ (varE && varF)) ... I think.

Immutability

As we strive for with µObjects - this style is immutable. We will be able to have an object for every evaluation. There IS an object for every evaluation even if we don't create a varible for it. Any value we hold - It's unmodified. While µObject practitioners, and functional programmers, are wondering why you'd do it any other way - Immutability is a challenge to get going. Existing code ends up needing a lot of refactoring, and it requires the whole team on board.

Maintainability

The whole objective of µObjects is maintainability. This is easier to maintain.
I have this statement from above varA && varB ^ varC || varD ^ varE & varF and there's a rare bug for it.
Some might see it right away. Others are not going to key off of it.
When these operations are encapsulation; we can't make this mistake. The knowledge of HOW to do an and is in one place - only one place.

Parting Thoughts

Is this the best way to solve primitive obsession? Hah - No. Give all of your concepts objects. That's the best way.
This is the least irritating way I've found to deal with the edge cases that need to act like primitives. A counter. I need to increment. I need to compare.
I could make a Counter object.... Which is an interesting idea... One of the issues is that we either do what I've done in the past; and what Yegor, the author of Elegant Objects, does in his Cactoos library and have a method to return the primitve.
I've used this for 8 months; it's a nice solution. C# has implicit casting operators for classes that I realized while working on the Bool fluent solution. This allows the code to read cleaner. Allows our custom types to BE a boolean. Our ints to BE ints (when needed). While this opens up the opportunity for abuse, the abuser will have to create a primitive variable to act on it as a primitve. This will stand out in the code.

Show Comments