|
I agree it's easy to go overboard with it. That's why I mentioned I feel like there has to be "more sauce" to the abstraction - e.g. an abstraction that abstracts multiple parameters, an abstraction that adds functionality, etc.
As a more concrete example with the Angle idea - you have Radians and Degrees as options (so Angle is basically an Either sum type) and Radians and Degrees are isomorphic.
Why is that useful? Here's some pseudo-code:
class Angle<T> = Radian<T> | Degree<T>
(+) :: Angle a -> Angle b -> Angle c
(+) x = match x
| Radian z => \y -> z + toRadian(y)
| Degree z => \y -> z + toDegree(y)
public double addRightAngle(double degrees) => degrees + 90; //Fails if you pass in radians
public double addRightAngle(double radians) => radians + (90*(pi/180)); //Fails if you pass in degrees
public Angle<double> addRightAngle(Angle<double> angle) => angle + new Degree(90); //Succeeds in all cases
public Angle<double> addRightAngle(Angle<double> angle) => angle + new Radian(1.5708); //Succeeds in all cases
How useful this is depends on how important angles are to your code-base, but I think abstracting inputs is very powerful. Another example is if you're doing functional programming and have a function that accepts impure inputs like a database function. You can group all impure inputs together into a tuple and shift that tuple to the right of the parameter list. 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 (i.e. "functional" dependency injection). Makes a lot of things easier especially unit testing. Credit to Mark Seemann for that insight[^].
modified 27-Sep-23 1:09am.
|
|
|
|
|
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.
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 a string and returning a function of int -> string , or as a function of two inputs (the tuple (string, int) ) that returns a string . 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 and Value and return a Bool . 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 the Database parameter, get the result function, then drop the database tables, then apply the Value , 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 the Value , I can't create a situation where the Database 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 the Database -> Bool that's returned.
I might be off on some stuff, always learning, but that's my understanding of it.
|
|
|
|
|
I think it's similar to the old "bool-vs-enum" arguments that have been around for many years. An appropriately-named enum can certainly make the calling code easier to read than a bunch-o-bools, although it can lead to some interesting definitions[^].
Given how recent the book is, I'm surprised the author hasn't progressed to record structs[^]; perhaps that will come later?
public readonly record struct Speed(double Amount);
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
modified 26-Sep-23 6:50am.
|
|
|
|
|
Richard Deeming wrote: An appropriately-named enum can certainly make the calling code easier to read than a bunch-o-bools, although it can lead to some interesting definitions[^]. In my student days, DEC had a discussion group system called COM, not unlike the far more well known NetNews, running on DEC-10 and DEC-20 mainframes. Whenever the software asked the user a yes/no question (such as "Delete entry?") there were in fact three options: Yes, No, Maybe. If the user chose Maybe, a random generator was used to choose between Yes and No.
This was a fully documented, well known feature.
I believe this feature existed in the old DEC-10/20 version. COM was completely rewritten as a platform independent system and renamed PortaCOM. I used the system in the transition from COM to PortaCOM; maybe the 'Maybe' option came with the PortaCOM rewrite. I didn't discover it until we had switched to PortaCOM.
|
|
|
|
|
I'm a total n00b here with record structs... but reference types that offer value-based equality checking? That's actually pretty cool.
Jeremy Falcon
|
|
|
|
|
He failed step 3. Angle is not very well implemented, as it does not specify what unit it uses. Either call it Radians, or specify the unit in the constructor and getter.
Ironically, this could possibly be implemented by inheritance. Angle being the base class and Degrees, Radians, Turns and Gons (optionally Longitude and Latitude) being the sub classes, although this would force it to be a class.
As implemented the client of this function could still create an angle of 90 (thinking degrees) and pass this in and get a point at 90 radians.
I agree that is is a good idea to make structs immutable. I have seen so many bugs caused by trying to alter a Point or Size but the code is altering a copy, not the original.
Is implementing structs for these a good idea? Possibly. I have implemented Angle as a class before - mainly for user display. I have also just used angleDegrees and angleRadians. Choose wisely.
The advantages are it forces the correct type to be passed in.
The disadvantages are a little bit of speed and extra code.
Would naming the angle as angleRadians be enough to eliminate most errors?
Do we need explicit casts on Radians so we can use (Radians)Math.PI?
Personally I prefer a plain double, named well, although an explicit type can be useful.
|
|
|
|
|
"I prefer a plain double, named well"
I agree. Working in embedded systems where units are very important, I try to always include units at the end of the variable name. It causes a mental check when you start misusing the variable. But I also like a little more rigidity to avoid really stupid errors. I'm thinking of the Mars Climate Orbiter that lawn darted due to a units conversion issue. Errors like this boggle my mind. Every engineering system should be in metric. Period. If you want to convert something to English - that's a presentation issue, but I digress.
This conversation is an excellent read.
Charlie Gilley
“They who can give up essential liberty to obtain a little temporary safety deserve neither liberty nor safety.” BF, 1759
Has never been more appropriate.
|
|
|
|
|
charlieg wrote: I'm thinking of the Mars Climate Orbiter that lawn darted due to a units conversion issue. A less known story of a similar kind: In its infancy in the late 1960s, the Norwegian computer manufacturer Norsk Data started buying standard 19" power supplies, and tried to build their own 19" racks. They ended up as being rather roomy, the power supplies almost fell down.
It was tracked down to the mechanics having calculated the metric equivalent of 19" on the old Norwegian inch of 25.6 mm, rather than the American 25.4 mm inch. 19 times 0.2 mm = 3.8 mm. Not terribly much extra, but enough to require some extra care for the power supplies to be securely fastened.
(According to Wikipedia, the SI inch definition of 25.4 mm was almost ten years old at the time, but it takes much more than ten years to change tooling and conversion factors. (And I am not going to bring up metrication in the US of A to prove my point.))
|
|
|
|
|
charlieg wrote: Every engineering system should be in metric. Period. If you want to convert something to English - that's a presentation issue, totally agree with that.
And I add...
Little vs Big Endian and similars. --> I have spent some time when looking for errors until I got used to make the check of inverting the significancy of the bytes when starting to work with two different systems I hadn't configured myself.
Signed vs unsigned values (most significant bit as the sign) --> You get to 32646, 32647 and then you see -1, -2, -3...
PLC String vs PC String --> PLC needs 2 bytes more "total length" and "used bytes" of current content (1st letter is String[2] position), having to transform to Array of Bytes to communicate
...
there are a lot of "funny" error sources out there
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.
modified 27-Sep-23 15:21pm.
|
|
|
|
|
Surely most of these problems can be circumvented by using sensible variable or parameter names, eg AngleInRadians", "ElapsedTimeInMillisec" etc? Saves a lot of coding time, and however you try to avoid it, if a developer is determined to be dumb, he or she will be, whatever.
|
|
|
|
|
|
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.
|
|
|
|
|
It seems like the sample the author provided is overkill for the passing of 3 simple variables.
What is the purpose of any of this, I have no idea, except to make programming more confusing to the developer.
What is wrong with passing a well-named variable as a primitive, especially with the hardware we work with today?
In any event, I use Structs\Structures to pass data and Classes to execute methods. However, unless there was a specific reason to pass certain data in a Struct\Structure (ie: such as the need to pass all of the data, which makes up a particular Struct\Structure, passing such data as individual variables makes a lot more sense then wasting time on creating Structs\Structures to do so...
Steve Naidamast
Sr. Software Engineer
Black Falcon Software, Inc.
blackfalconsoftware@outlook.com
|
|
|
|
|
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.
|
|
|
|
|
I once worked with a programmer 28 years ago who was approaching retirement. We worked on an accounts receivable/payable system together. He did all of the backend calculations in exactly the same format as step 1. If I remember correctly even his function names were single letters of the alphabet. I think I would have done at least step 2. I thought it was the simplest code you could possibly write. Back then there was no intellisense so we just documented our code thoroughly! I don't see any value in step 3 or 4. Seems like unnecessary layers of code.
|
|
|
|
|
John Chadwell wrote: I don't see any value in step 3 or 4. Seems like unnecessary layers of code.
You have much experience also and most likely that means you have developer discipline (do things right the first time so you don't shoot yourself in the foot later).
Developer discipline doesn't seem like it is being taught in modern times -- probably influenced by dynamic languages like JavaScript which also encourage people to "type" code (not design it and write it).
So, for this modern world i believe the movement is to "put the discipline in the code" so those who come after are "forced" to use it the way we have designed it. In the past, we would've read the legacy code and kept the parts that seem right.
Also, this is just the latest shiny.
|
|
|
|
|
Complete Novas HERE
When I look at Wrap All the Primitive Types In Structs
This looks logical to me ?
That said it took me a little time to learn to use {get;set;} example below
so my question is this code structure a good design or as stated just a fancy new way to write your code
If this is considered a programming question sorry just delete the post. I try to follow the rules honest
I did read all the replies
namespace CRUDLibrary
{
public partial class frmCRUD : Form
{
public static string dbName = "Contacts.db";
private string result;
public string fn { get; set; }
public string ln { get; set; }
|
|
|
|
|
Just curious where did you manage to get that book. Everywhere I search for it says it is pre-order to be delivered by the end of next month.
- Leonardo
|
|
|
|
|
Leonardo Pessoa wrote: Just curious where did you manage to get that book.
I'm a card-carrying member of oreilly.com -- ebooks from hundreds of publishers (manning, no starch, ms press, O'Reilly, Wiley, Addison-Wesley, -- to many to name) with 70,000 books available.
Most of the time the ebook releases before the book even releases and it is available on oreilly.com
You can try it for 10 days at this link (Create Your Trial: O'Reilly[^])
Disclaimer: I have no connection with oreilly and that is not an affiliate link.
The access to all 70,000 books and X,XXX number of videos is unlimited. It's quite amazing.
FYI - I've been a member of Oreilly since 2002 (when it only cost $10/month). It finally went to $19.95 / month and when they converted my account to their new system they let me grandfather into the old price $19.95 / month, but I know it is quite a bit more expensive now. Something like $39.95 / month or so.
|
|
|
|
|
I'm kind of against the whole "make your code more readable...at the cost of performance and efficiency". Programmers of the future who cannot understand the context of the past (and therefore the code) are of little concern. It might be that, as an engineer, my first stance is that all newcomers must be educated (a little) and eventually they should learn to educate themselves (a lot).
It harkens back to the guilds...but, except for being exclusive, there wasn't very much else wrong with their educational system.
So if someone wants to break OOP, they should write their own language and let everyone else be. I'm just happy I don't have to remember if that string of bytes was an integer or a floating point value (and I use the wrong mnemonic for multiplication). If you don't like inheritance, don't use it.
|
|
|
|
|
I agree with your answer.
Here's a reply I said to someone else that is about developer discipline related to this topic:
You have much experience also and most likely that means you have developer discipline (do things right the first time so you don't shoot yourself in the foot later).
Developer discipline doesn't seem like it is being taught in modern times -- probably influenced by dynamic languages like JavaScript which also encourage people to "type" code (not design it and write it).
So, for this modern world i believe the movement is to "put the discipline in the code" so those who come after are "forced" to use it the way we have designed it. In the past, we would've read the legacy code and kept the parts that seem right.
Just as you said,
Juan Pablo Reyes Altamirano wrote: my first stance is that all newcomers must be educated (a little) and eventually they should learn to educate themselves (a lot).
Unfortunately, there seem to be people moving into Software Development that just think they can start typing and produce a program.
|
|
|
|
|