The changing landscape of OOP (from class to struct)
-
Chris Baker 2021 wrote:
Is "prefer composition over inheritance" functional?
In functional programming that concept is talked about a lot. I mean a lot. Mainly because in purely functional programming you don't have inheritance. So, when I speak of composition, I'm specifically referring to functional composition. [Function composition (computer science) - Wikipedia](https://en.wikipedia.org/wiki/Function\_composition\_(computer\_science)) There's also object composition in OOP-land. Not really sure which one came first though...
Jeremy Falcon
Jeremy Falcon wrote:
Not really sure which one came first though
I'm reasonably sure functional composition came first. I think Lisp predates the OO paradigm by quite a bit. And if I remember correctly, in the early days, Lisp was purely functional.
Keep Calm and Carry On
-
Jeremy Falcon wrote:
Not really sure which one came first though
I'm reasonably sure functional composition came first. I think Lisp predates the OO paradigm by quite a bit. And if I remember correctly, in the early days, Lisp was purely functional.
Keep Calm and Carry On
Thanks for that. That's what I thought too but wasn't sure.
Jeremy Falcon
-
Because I have a brain? :~:~
Jeremy Falcon
Jeremy Falcon wrote:
Because I have a brain and I know how to use it for that rare thing called critical thinking ? :~ :~
FTFY (Sorry, I forgot the /s at the end of my previous comment)
M.D.V. ;) If something has a solution... Why do we have to worry about?. If it has no solution... For what reason do we have to worry about? Help me to understand what I'm saying, and I'll explain it better to you Rating helpful answers is nice, but saying thanks can be even nicer.
-
Jon McKee wrote:
Here's some pseudo-code:
Got it. I didn't think of it in the context of replacing overloads. Just calling it that because if I where to code up your first two calls I'd at least have two strong (primitive-based) types that would differentiate the signature. I've been in JavaScript too long where that's not really done. :-O
Jon McKee wrote:
This effectively turns your function into a pure function that returns an impure Reader with that environment tuple as input and the result as output
That one I gotta look into man. My understanding of pure functions is that all inputs are deterministic. So, not following how shifting parameter order changes that, since non-deterministic input is still going into the routine. Will check out the link though. Btw, thanks for knowing what you're talking about. Makes these conversations much better.
Jeremy Falcon
Jeremy Falcon wrote:
Btw, thanks for knowing what you're talking about. Makes these conversations much better.
Haha, thanks, but I don't think I deserve that quite yet. I'm still learning from people like Bartosz Milewski and Mark Seemann.
Jeremy Falcon wrote:
That one I gotta look into man. My understanding of pure functions is that all inputs are deterministic. So, not following how shifting parameter order changes that, since non-deterministic input is still going into the routine. Will check out the link though.
This is an interesting topic that really broke my brain when I first ran into it. So, functions of more than one input have a lot of equivalent representations. For example,
string -> int -> string
can be seen as a function taking astring
and returning a function ofint -> string
, or as a function of two inputs (the tuple(string, int)
) that returns astring
. The important part with regards to purity is binding order, or in other words "what is provided when". You can only act upon what is provided, so if arguments that are (potentially) impure are not provided yet, the function is still pure. For example:public bool saveToDatabase(Database db) => val => { db.save(val) };
public bool saveToDatabase(Value val) => db => { db.save(val) };The first function is impure, the second function is pure. Why? They both take a
Database
andValue
and return aBool
. Both are lazy (i.e. they only evaluate when all arguments are supplied). Well, because purity is a logical result of inputs and outputs. In the first example, if I apply theDatabase
parameter, get the result function, then drop the database tables, then apply theValue
, the operation fails. The partially applied function is impure. The database object that was already bound (partially-applied) was side-effected by the tables dropping. In the second example, no matter what I do after applying theValue
, I can't create a situation where theDatabase
is invalid AFTER applying it. The returned function itself is impure since we're side-effecting a database, but the original function is not, because there is no way to change theDatabase -> Bool
that's returned. I might be off on some stuff, always learning, but that's my understanding of -
Just started reading this (just released) book, The C# Type System (no starch press)[^] and the first chapter is kind of blowing my mind. Step 1 Author starts out with the following example and says, "You must use better named variables so dev users know what they mean."
Displacement(double t, double v, double s)
{
var x = v * s * Math.Cos(t);
var y = v * s * Math.Sin(t) - 0.5 * 9.81 * Math.Pow(s, 2);
return (x, y);
}Yes, that makes sense. Step 2 Then he says, "Oh, you can add meaning with this new idea of named arguments so users don't have to remember order that they should be passed in."
var result = Displacement(angle: .523, speed: 65, elapsedTime: 4);
Ok, yes, that is good advice with the modern capabilities. Step 3 May Blow Your Mind He mentions that the code is still confusing because all three arguments are the same primitive type (double) and this leads into...
From the book:
Primitive Obsession code smell, which describes any code that has an overreliance on primitive types—that is, those types that are built into the language, such as int, double, and string.
The solution is... Wrap All the Primitive Types In Structs 🤯🤯🤯🤯🤯
public struct Angle
{
public double Size {get; set;}
}
public struct Speed
{
public double Amount {get; set;}
}The Paradigm Has Shifted Now, when the user attempts to call the
Displacement
method the compiler will know that the argument type is wrong. Now, there's no way to pass the wrong value into the method, because the compiler will know the type. Wow, that is a very different paradigm!! Step 4 Is Immutability Now, make each struct immutableI would not say it's a new concept, DDD introduced the concept of value objects a while back. Strong typing a value is how many implement value objects in C#. The logical continuation for this concept should be: 1- Add validation, what is the specific range of value that are acceptable to your function. In your example, should you accept a speed of 62123.25? 2- Add units, is it always the same unit, or should you convert from different units? 3- Displacement, Angle, Speed are very vague, are we talking about vehicles on a public road? or a spacecraft? Maybe we should use VehicleSpeed instead? 4- Maybe VehicleSpeed should derive from a base Speed class so you don't repeat units and conversions all the time? But you could add validation of acceptable speed value for a vehicle? And we're back to the good old OOP. Should you calculate the Displacement in the VehicleSpeed class? I don't think so, this should be composition, but the encapsulation of the speed value, it's units and validation into a class is definitely desirable. It is a question of perspective. We tend to work with good enough, but if you want accuracy, I would debate that those are the things you NEED to do. The reasons why most of us don't go the Value Object route are: That's a lot of work. It takes time, we're in a hurry, it makes code base bigger, some would say more complex (I disagree). Is Primitive Obsession a code smell? I say YES! But are we willing and able to do everything that is required to REALLY fix it?
Christian
-
Just started reading this (just released) book, The C# Type System (no starch press)[^] and the first chapter is kind of blowing my mind. Step 1 Author starts out with the following example and says, "You must use better named variables so dev users know what they mean."
Displacement(double t, double v, double s)
{
var x = v * s * Math.Cos(t);
var y = v * s * Math.Sin(t) - 0.5 * 9.81 * Math.Pow(s, 2);
return (x, y);
}Yes, that makes sense. Step 2 Then he says, "Oh, you can add meaning with this new idea of named arguments so users don't have to remember order that they should be passed in."
var result = Displacement(angle: .523, speed: 65, elapsedTime: 4);
Ok, yes, that is good advice with the modern capabilities. Step 3 May Blow Your Mind He mentions that the code is still confusing because all three arguments are the same primitive type (double) and this leads into...
From the book:
Primitive Obsession code smell, which describes any code that has an overreliance on primitive types—that is, those types that are built into the language, such as int, double, and string.
The solution is... Wrap All the Primitive Types In Structs 🤯🤯🤯🤯🤯
public struct Angle
{
public double Size {get; set;}
}
public struct Speed
{
public double Amount {get; set;}
}The Paradigm Has Shifted Now, when the user attempts to call the
Displacement
method the compiler will know that the argument type is wrong. Now, there's no way to pass the wrong value into the method, because the compiler will know the type. Wow, that is a very different paradigm!! Step 4 Is Immutability Now, make each struct immutableI'm sure there are some good points, and that many of my objections may be only because such simple examples are provided, but as presented it rubs me the wrong way. I'm sure the author isn't saying "do this all the time". I am unlikely to create a single-field or -property class or struct without also adding Methods. At least a custom ToString. Or make it IComparable to itself. Then there is some actual benefit. My experience hasn't needed such things, but I could see separate structs for OunceTroy and OunceAvoirdupois, for instance. Yet I would not want to see someone making a struct for each time zone (use DateTimeOffset for that). Two thoughts: 0) It looks like it can lead to a form of Hungarian Notation, but one with compiler support and protection. 1) If not C#, maybe another language would use this type of thing to provide compile-time safety, but decompose such simple structures to the underlying primitive types for improved speed. Maybe at JIT time?
-
I would not say it's a new concept, DDD introduced the concept of value objects a while back. Strong typing a value is how many implement value objects in C#. The logical continuation for this concept should be: 1- Add validation, what is the specific range of value that are acceptable to your function. In your example, should you accept a speed of 62123.25? 2- Add units, is it always the same unit, or should you convert from different units? 3- Displacement, Angle, Speed are very vague, are we talking about vehicles on a public road? or a spacecraft? Maybe we should use VehicleSpeed instead? 4- Maybe VehicleSpeed should derive from a base Speed class so you don't repeat units and conversions all the time? But you could add validation of acceptable speed value for a vehicle? And we're back to the good old OOP. Should you calculate the Displacement in the VehicleSpeed class? I don't think so, this should be composition, but the encapsulation of the speed value, it's units and validation into a class is definitely desirable. It is a question of perspective. We tend to work with good enough, but if you want accuracy, I would debate that those are the things you NEED to do. The reasons why most of us don't go the Value Object route are: That's a lot of work. It takes time, we're in a hurry, it makes code base bigger, some would say more complex (I disagree). Is Primitive Obsession a code smell? I say YES! But are we willing and able to do everything that is required to REALLY fix it?
Christian
Very interesting points...and you should read that book, because that is exactly the angle the author is taking. Chapter 1 was very good, challenging and interesting and has definitely changed my mind on many things. I think using these ideas at the right time will make better code. Just started chapter 2 and it is continuing to be interesting so this is a really great book bringing together a number of interesting ideas.
-
For me, C# lacks the easy way of custom types provided by Object Pascal, especially limited-length strings/etc. like
type
ShortStr = string[255];
TTextBuf = array[0..127] of Char;
TElfWordTab = array [0..2] of Cardinal;IIRC that's part of the original Pascal specification. When I started with C# (corporate edict) in about 2003/4 that's the first feature I missed badly.
-
To be fair, this was the beginning of the example, but the main thing you get is the compiler can check the type and warn you that you are using the wrong type. This also means the code is quite a bit more explicit about what it wants so a future dev who may have thought, "hmm....wonder why orig-dev wanted a double when a float would do" would be guided away from such a thought since there is a specific type. That's all I got. :)
Orig-dev could/should have provided a comment if choice of the primitive type was significant. "Keep it as simple as possible, but no simpler". Many developers have a tendency to introduce complexity for the sake of it, because it looks clever, it's an interesting exercise, or other similar reason, when it achieves little but obfuscation.
-
Just started reading this (just released) book, The C# Type System (no starch press)[^] and the first chapter is kind of blowing my mind. Step 1 Author starts out with the following example and says, "You must use better named variables so dev users know what they mean."
Displacement(double t, double v, double s)
{
var x = v * s * Math.Cos(t);
var y = v * s * Math.Sin(t) - 0.5 * 9.81 * Math.Pow(s, 2);
return (x, y);
}Yes, that makes sense. Step 2 Then he says, "Oh, you can add meaning with this new idea of named arguments so users don't have to remember order that they should be passed in."
var result = Displacement(angle: .523, speed: 65, elapsedTime: 4);
Ok, yes, that is good advice with the modern capabilities. Step 3 May Blow Your Mind He mentions that the code is still confusing because all three arguments are the same primitive type (double) and this leads into...
From the book:
Primitive Obsession code smell, which describes any code that has an overreliance on primitive types—that is, those types that are built into the language, such as int, double, and string.
The solution is... Wrap All the Primitive Types In Structs 🤯🤯🤯🤯🤯
public struct Angle
{
public double Size {get; set;}
}
public struct Speed
{
public double Amount {get; set;}
}The Paradigm Has Shifted Now, when the user attempts to call the
Displacement
method the compiler will know that the argument type is wrong. Now, there's no way to pass the wrong value into the method, because the compiler will know the type. Wow, that is a very different paradigm!! Step 4 Is Immutability Now, make each struct immutableWhile I can see the value in some corner cases perhaps, I feel like the acceptor, such as a parameter name on a method should describe what it accepts. Maybe it's just because I grew up with that, but putting the units for example, on the data type itself seems just ... unusual, and kind of awkward to me. Maybe that's not the best argument against it, but I place a lot of value on being able to lean on habits, so long as the habits aren't actually counterproductive. All else considered equal, I place more value in doing something in a way that's supported by "muscle memory" than I do on implementing something perhaps in a more formalized way, and the reason is, if you can lean into your habits, as long as you develop decent habits you get less bugs and more productivity.
Check out my IoT graphics library here: https://honeythecodewitch.com/gfx And my IoT UI/User Experience library here: https://honeythecodewitch.com/uix