Hmm...
Fluent types vs Type Types
awaiting vs method
As part of the Principle of MicroObjects, Represent ALL Concepts, we still need to manipulate the data; at some point; or pass data to 3rd party libraries as primitives. We need to get the raw data.
How do we do get to raw data?
We can't have getters. That's a violation of our practices.
We can't use callbacks in all situations, We may not know... Or we do know... Well shit... This may negate my WHOLE need for this post... Using callbacks. Can I use callbacks?
Hmmm - Callbacks...
We use them for UI elements...
OK, I'm thinking through an example where I need value from a few objects.. I'd have to have something akin to the Visitor Pattern or Builder Pattern. Both of these are helpful; but are the active noun, -or and -er, words. Things we want to avoid. They tend to be messy.
OK - So... Looking at some things in a bit of code; definitely challenges to using a callback EVERYWHERE. I'm going to keep that on the back burner as I play with new ideas... but it's not jumping out as a solid new solution... phew... I still have a post to write.
No Callbacks... yet.
OK, back to the post. No Getters. No Callbacks (for everything). Can't wrap in a new object; that's just a new thing we need it out of...
How do we get the data out?
As 3rd Party Code
In the end we need to get to get 3rd Party objects. I've had a few ways to do this. The one I've been using started with my FluentTypes. It's a lesser used feature of C#; implicit operator
.
It looks something like this
public abstract class Text
{
public static implicit operator string(Text origin) => origin.Value();
protected abstract string Value();
}
The compliler can detect and do the cast for us. It's nice. We have no access to the data in OUR code unless we cast. Casting SHOULD stand out in the code. It should be questioned. It's a 'safety' mechanism on this technique.
This works well. Except there was an itch in my head with it. It's not a string
, it's a Text
. There's plenty of advantages to using it - but it's still 'data'. It's not actually representing the concept.
These were developed before I hit the realization that I was trying to Represent all Concepts in the code. The were better than raw data; but not something I was content with.
Beyond Fluent Types
As I explored more; I still used the FluentTypes in the code. I still do. Even in my newer styles. I think the advantages that I get from FluentTypes is still a value, but Text
is not an object that should exist across the system. It's to control/limit data exposure and manipulation.
The next style I tried is a missed realization of Concepts. We represent what we want. (So close to 'concept')
In one case we had to generate a 'Document Control Number'. With just FluentTypes, this would be a chain of responsibility that returned a Text
object. The rest of the system interacts with Text
. Nothing knows that it's a "Document Control Number".
This next step created a DocumentControlNumber
object that was passed around. Internally it had a chain of responsibility that would do the generation, but that was only exposed through an implicit operator
in the DocumentControlNumber
. This works well. Most of the time.
async
/await
There's one situation where I ran into a lot of trouble with the implicit operator
cast - await\async
.
You CAN make the implicit operator
work with async. It's a little odd...
Text text = new TextOf("Blort");
string actual = await text;
The strange thing here is the await text
. Awaiting an object... not a method... huh... Strange.
In my usages, The FluentTypes need to extract values which adds a lot of await
in strange places. I was never happy with this.
It's also VERY differnt technique. Using await
on an object... that's very different.
We ended up not using it. We didn't like using an await
on objects. We had some workarounds for our specific case to get around the async/await
issues.
Next steps?
I hope I never stop experimenting with different ways to do things. I want to find different ways to do things and understand the pain/power of each of them.
The next step I went with is a base class that allows me to have a common way to extract 3rd party types.
[DebuggerDisplay("{ToSystemType()}")]
public abstract class ToSystem<TSystemType>
{
protected abstract Task<TSystemType> Value();
public async Task<TSystemType> ToSystemType() => await Value();
}
I accept it's kinda a getter... but it'll stand out. We can actually check all usages of ToSystemType
and make sure it's being used in acceptable places. I feel this is an advantage to it.
We don't need the cast, explicit or explicit.
I'm not sure if I like
foo.blah(textValue);
or
foo.blah(textValue.ToSystemType());
better.
The extract method call is kinda annoying. It's 'redundant... ish.
Actually it's
foo.blah(await textValue.ToSystemType());
The use of await
and ToSystemType
together make it VERY verbose. I'm not thrilled with verbosity when I don't need it. I need await
... do I need ToSystemType
?
I played around a bit more and got this base class to act as a 'custom awaiter'.
[DebuggerDisplay("{ToSystemType().Result}")]
public abstract class ToSystem<TSystemType>
{
protected abstract Task<TSystemType> Value();
public TaskAwaiter<TSystemType> GetAwaiter() => Value().GetAwaiter();
}
This looks like
foo.blah(await customObj);
The downside of this is that I'm awaiting on an object. Positive - I can use await/async
without repeating work arounds. It's in the base class.
Upside; less verbose.
In my usage so far; await
ing on an object only occurs at the edge of the system. Since it's almost entirely at the edge of the system; we're isolating the smell. I won't try to say it doesn't smell - It does. It feels odd... but it simplifies the my usage of async\await
.
That's a huge thing with systems moving forward. I need the code to async\await
smoothly. Complexity in code doing that will reduce the effectiveness and ease of threading.
It was dealing with a Semaphore
that drove me to the GetAwaiter
method. I was having to create nearly identical classes for async or non-async. I didn't like that. It was a lot of re-work and potential for mistakes and bugs. Can't have code I'm uncertain about. I decided to drop the multiple classes and force everything to be async
. There's little in my most recent code that's not await
able.
It's quick and consistent.
While I think it's odd to await a type... It's not terrible in this situation.
This is a recent change to my objects base. I don't know what I'll run into with it... I'm looking forward to opportunities to struggle.