Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Phobeste
Apr 9, 2006

never, like, count out Touchdown Tom, man

Absurd Alhazred posted:

This is the "if you access our website you have signed the EULA" rider of type systems.


No. It is a fundamental difference of approach to type systems. It is called "structural subtyping" or "row-based polymorphism". It is the kind of type system used (mostly poorly but increasingly well) by C++ template functions, rust traits, and haskell. Structural subtyping is bottom up: the identity of a type is its attributes' type (and sometimes name or position) and its methods and their signature (and sometimes their name or position) (in an object-oriented language). A polymorphic relationship is defined and fulfilled by common structure between two types. In fact, there isn't really such thing as "defining a type" in a structural subtyping system: you only define constraints on structure at the point you use a type. If you define A = {string s} and B = {string s}, these are the same because they have the same structure. It's a better metaphor to think of A and B as aliases for the type {string s} than as capital-t Types themselves: the capital-t Type is `{string s}`. Aliases are useful for condensing and reusing code - it's a lot easier to write func f(a: A) => A than func(f(a: {string s}) => {string s}) especially as types get more complex, but in a structural subtyping system these are both exactly equivalent, and exactly explicit.

This principle extends to subtyping. {string s, string t} (let's alias this to B, though i'm not doing it in the preformat section to drive home that aliases are optional and types stand on their own) is a subtype of {string s} (alias it to A, same caveat as before) because, well, it is - it contains A entirely and thus can be used wherever A can be used.

The more familiar approach to type systems to most programmers, evidently including you, is "nominative subtyping" or column-based polymorphism. Nominative subtyping is the approach used by C++ functions, Java pre-generics, C#, and so on. Nominative subtyping is top down: the identity of a type is established by the programmer, and then structure is bound to that name. The name is the identity. Here, if you define A = {string s} and B = {string s} these are different, because you the programmer have explicitly given them different identities. Doing func f(a: A) => A[/fixex], [fixed]func f(a: B) => B, and func f(a: {string s}) => {string s} are not equivalent, and the latter is often not allowed because the inline type definitions don't really have an identity - certainly not an identity outside the definition of the function.

As with structural subtyping, the concept of programmer-established identity in a nominative system also extends to subtyping. B = {string s, string t} is not a subtype of B = {string s} because you the programmer have not told the compiler it is; you have to do B = A + {string t} or however your language spells it.

Familiarity being limited to nominative typing systems is probably what leads you to thinking that the nominative subtyping equivalent is "more explicit" than the structural subtyping equivalent. To someone familiar with structural subtyping, {string s, string t} being a subtype of {string s} is certainly explicit, because hell - look at it, B explicitly contains the structure of A. If you didn't want that, you would have made it {string u, string t} or something. If your language doesn't consider the name of an attribute part of the structure of the language (which, by the way, typescript does) then you should explicitly give it a tag or something. The thing that someone who is familiar with structural subtyping considers implicit is inferred types from usage, like being able to do something like func f(a) { return g(a) }; func g(a) {return a) without explicitly specifying constraints and having the compiler just figure out that you want As in there.

Criticizing a structural subtyping system because it considers types of identical shape equivalent even if they have different names is like criticizing a nominative subtyping system because it considers types of identical shape different just because they have different names. These are orthogonal designs that use completely different methods to arrive at the same goal: a sound type system that provides programmers the ability to verify parts of their programs' correctness, and provides compilers hints about how to correctly generate efficient code.

Phobeste fucked around with this message at 21:48 on Sep 26, 2022

Adbot
ADBOT LOVES YOU

Foxfire_
Nov 8, 2010

Analogy:

C++ code:
bool Foo(string& name1)
{
  string& name2 = name1;
  string& name3 = name2;
  return (name1 == name2) && (name2 == name3);
}
All of those are names that refer to the same string. There is no one true variable name for the string. All the names you introduce are just different ways to refer to the same platonic string.
Similarly, in structural subtyping, types have no one true name. All the names you introduce are just quick ways to refer to the same type floating in conceptual land

FlapYoJacks
Feb 12, 2009
People who use char* foo instead of char *foo are bad people.

omeg
Sep 3, 2012

FlapYoJacks posted:

People who use char* foo instead of char *foo are bad people.

The horrors come from the thread.

Absurd Alhazred
Mar 27, 2010

by Athanatos
With all the stupid garbage they poured into C++20, it would have been nice if they had made * and & first class type modifiers instead of variable-attached type modifier.

Volte
Oct 4, 2004

woosh woosh

Absurd Alhazred posted:

With all the stupid garbage they poured into C++20, it would have been nice if they had made * and & first class type modifiers instead of variable-attached type modifier.
That would be backwards incompatible.

Absurd Alhazred
Mar 27, 2010

by Athanatos

Volte posted:

That would be backwards incompatible.

Yes?

Phobeste
Apr 9, 2006

never, like, count out Touchdown Tom, man

Absurd Alhazred posted:

With all the stupid garbage they poured into C++20, it would have been nice if they had made * and & first class type modifiers instead of variable-attached type modifier.

What parts of what got added to c++20 do you consider stupid garbage? This isn't a gotcha I'm just curious. I like most of it but I really wish ranges didn't carry the originating type forever.

Absurd Alhazred
Mar 27, 2010

by Athanatos

Phobeste posted:

What parts of what got added to c++20 do you consider stupid garbage? This isn't a gotcha I'm just curious. I like most of it but I really wish ranges didn't carry the originating type forever.

Most of this is uninteresting and I don't see myself using it in my own code. Spaceship comparison! As if C++ wasn't reusing enough punctuation marks in weird ways. "Contains" as a replacement to "find"! But "find" also gives you an iterator to something to work with if it succeeds, which is usually what I want!

Presto
Nov 22, 2002

Keep calm and Harry on.
Contains isn't replacing find. It's in addition to find.

Absurd Alhazred
Mar 27, 2010

by Athanatos

Presto posted:

Contains isn't replacing find. It's in addition to find.

Fine, it's not a replacement, it's just a weird addition.

Foxfire_
Nov 8, 2010

Absurd Alhazred posted:

Fine, it's not a replacement, it's just a weird addition.
I can't think of any other language that has mapping types without an analogous operator. C#, Java, Python, Rust, and JavaScript all have that. Not having it is weird.

Spaceship is strange, but it's entire purpose in life is to make it easy to define object ordering without having to explicitly write operator==, operator!=, operator<, operator<=, operator>, and operator>=. It is convenient for that, and you probably shouldn't use it otherwise.

Volte
Oct 4, 2004

woosh woosh
I frankly would prefer that the C++ ecosystem didn't get hard rebooted. I've had enough of that with .NET thank you.

Presto
Nov 22, 2002

Keep calm and Harry on.

Absurd Alhazred posted:

Fine, it's not a replacement, it's just a weird addition.
It's not. It's a nice way to avoid having "if (myButtMap.find(datAss) != myButtMap.end())" all over the place.

qsvui
Aug 23, 2003
some crazy thing
Is anyone actually using modules with C++20 yet?

Phobeste
Apr 9, 2006

never, like, count out Touchdown Tom, man

qsvui posted:

Is anyone actually using modules with C++20 yet?

I don't think most compilers have implemented it. I think msvc is the farthest along but also they started with their own slightly disjoint implementation.

Absurd Alhazred posted:

Most of this is uninteresting and I don't see myself using it in my own code. Spaceship comparison! As if C++ wasn't reusing enough punctuation marks in weird ways. "Contains" as a replacement to "find"! But "find" also gives you an iterator to something to work with if it succeeds, which is usually what I want!

My thoughts are only partially aligned with yours.

C++ Concepts library: this may not be your bag since its purpose is making the structural subtyping system provided by templates easier. But if you like and are comfortable with it, concepts is an incredibly convenient way to express template substitution constraints and a regularization and implementation into the language of the old "named concepts" (like ForwardIterator) that were by and large just documentation constructs.

3-way comparisons: I wouldn't use this in like a for loop in application code but like others have said it's pretty useful to implement in a library object to define total ordering with less god drat typing... and that's sort of a theme of a lot of the other stuff here:

Map contains
Range-based for loop
std::string functions
std::to_array

These things exist because incredibly simple operations that are standard in most languages are incredibly painful in c++. Those std::string functions are starts_wtih and ends_with. to_array makes it possible to initialize an array in a way that doesn't make you want to uh shall we say post in cspam. adding initializer-statements to range-based for makes it much easier to do just-in-time container definitions for small loops. these are things I will and actually have used in production or more commonly in tests since they require allocation and they own immensely.

Array bounded/unbounded - useful in libraries I think

New identifiers ( import, module) - Haven't used this, don't think anything implements it, remains a bit of a ???? about how useful it'll actually be
Calendar and time zone library - Mostly using cpp in embedded micros so not useful

Likely and unlikely attributes - pretty sick in architectures where it's relevant, c++ has been moving forward in standardizing attributes so you can do less of the insanely stupid #if GNUC #define MYORG_LIKELY __attribute__((likely)) #endif poo poo and it's so good

C++ is finally in a place where the committee is passing papers that have "for fucks sake this makes everybody's lives so much easier for a pittance of implementor effort" as their rationale (see also: #embed) and it is so goddamn nice and it's stuff that's really easy to start using

Plorkyeran
Mar 22, 2007

To Escape The Shackles Of The Old Forums, We Must Reject The Tribal Negativity He Endorsed
I don't care about any of the "major" C++20 features other than Concepts, but the "minor" ones are a giant list of things that really should have been in C++03 (or whatever version the relevant feature was added in). C++ has an absurd number of ways that it's unnecessarily annoying to use, and C++20 is finally trying to address routine ergonomics problems. No one's life is going to be changed by not having to manually define operator != any more, but it was stupid that we had to in the first place and better late than never to fix that.

take boat
Jul 8, 2006
boat: TAKEN

duck monster posted:

No. It would make him my boss, for all of the mimimum time it takes to see out my contract notification period, and likely the entire teams notification period. I've told the boss that I consider it a move that would be a company killer.

And the boss has now been made told the same by pretty much everyone on our team.

Also I sat down with the boss and demonstrated completely pwning a demo install of his "improved" TCP stack including burning down the database. The boss agreed to me running the new stack through the load tester even if new guy objects (He had been objecting for a while. Because the old stack was stable up to around 20K connections, (while I know from private tests the new one seems to start OOMing the VM after about 30 connections from all the race conditions and other horrors that happened when innexperieced people try and roll their own servers). New guy is informed this will be to test how it handles under 10-20K client conditions. I expect this to be a blood bath.

They might be transfering him out to the engineering team since he spends almost his entire time there instead of doing his job.

Good luck trying to terrorise our hardware guys with THAT level of coding incompetence, Our main embedded guy is a *lot* less polite than I.

well that sucks, I was definitely wrong in asking if moving to project manager would mean he'd managing bureaucracy without power over engineering

how was he being considered for a software eng management role and as a fallback is being moved to hardware engineering? that seems like even more of a horror

Falcon2001
Oct 10, 2004

Eat your hamburgers, Apollo.
Pillbug

take boat posted:

well that sucks, I was definitely wrong in asking if moving to project manager would mean he'd managing bureaucracy without power over engineering

how was he being considered for a software eng management role and as a fallback is being moved to hardware engineering? that seems like even more of a horror

This dude sounds like he's not just the boss's kid, he's holding the boss's kid hostage or something, jesus christ.

Athas
Aug 6, 2007

fuck that joker

Phobeste posted:

No. It is a fundamental difference of approach to type systems. It is called "structural subtyping" or "row-based polymorphism". It is the kind of type system used (mostly poorly but increasingly well) by C++ template functions, rust traits, and haskell.

Haskell is nominally typed, and doesn't support row polymorphism. You can perhaps encode it in some roundabout way via type classes, but that's cheating.

Xarn
Jun 26, 2015

FlapYoJacks posted:

People who use char* foo instead of char *foo are bad people.

omeg posted:

The horrors come from the thread.

Xarn
Jun 26, 2015

Plorkyeran posted:

I don't care about any of the "major" C++20 features other than Concepts, but the "minor" ones are a giant list of things that really should have been in C++03 (or whatever version the relevant feature was added in). C++ has an absurd number of ways that it's unnecessarily annoying to use, and C++20 is finally trying to address routine ergonomics problems. No one's life is going to be changed by not having to manually define operator != any more, but it was stupid that we had to in the first place and better late than never to fix that.

The issue with all the changes around <=> and comparison reordering and stuff is that they can silently cause new and cool issues with previously valid code.

It would be great if it was in for C++98/03, so that people would write valid code for it, but since we get it in C++20, it will be lot of "fun" to update everything.

Volte
Oct 4, 2004

woosh woosh

Xarn posted:

The issue with all the changes around <=> and comparison reordering and stuff is that they can silently cause new and cool issues with previously valid code.

It would be great if it was in for C++98/03, so that people would write valid code for it, but since we get it in C++20, it will be lot of "fun" to update everything.
What issues is <=> going to cause (other than a potential lexer quirk that will in practice almost never come up)?

Bongo Bill
Jan 17, 2012

Athas posted:

Haskell is nominally typed, and doesn't support row polymorphism. You can perhaps encode it in some roundabout way via type classes, but that's cheating.

Haskell is a dynamically-typed, interpreted language.

Xarn
Jun 26, 2015

Volte posted:

What issues is <=> going to cause (other than a potential lexer quirk that will in practice almost never come up)?

C++ code:
#include <utility>
#include <iostream>
#include <cstring>

struct S {
    const char* a;

    operator const char* () const {
        return a;
    }

    friend bool operator<(const S& s1, const S& s2);
};

bool operator<(const S& s1, const S& s2) {
    return strcmp(s1.a, s2.a) < 0;
}

int main() {
    S s1 = {"A"};
    char xx[2] = { 'A', '\0' };
    S s2 = {xx};

    std::pair p1{ s1, s2 };
    std::pair p2{ S{"A"}, S{"A"}};

    if( p1 < p2 ){
        std::cout << "p1 < p2\n";
    }
    if( p2 < p1 ){
        std::cout << "p2 < p1\n";
    }
    if( !(p2 < p1) && !(p1 < p2) ){
        std::cout << "p1 == p2\n";
    }
}
The output is different with C++17 and C++20.

Zopotantor
Feb 24, 2013

...und ist er drin dann lassen wir ihn niemals wieder raus...

FlapYoJacks posted:

People who use char* foo instead of char *foo are bad people.

Agreed. Also int& fee is wrong and int &fee is correct.

The */& binds to the object, to declare two pointers you have to write * twice.
(And the common coding guideline that says to only declare one name per declaration only exists because there are people who don't understand that.)



Sorry, pet peeve.

FlapYoJacks
Feb 12, 2009

Zopotantor posted:

Agreed. Also int& fee is wrong and int &fee is correct.

The */& binds to the object, to declare two pointers you have to write * twice.
(And the common coding guideline that says to only declare one name per declaration only exists because there are people who don't understand that.)



Sorry, pet peeve.

:haibrow:

Volte
Oct 4, 2004

woosh woosh

Xarn posted:

The output is different with C++17 and C++20.
Interesting example. I assume that's because the implicit conversion to const char * is tricking std::pair into thinking the spaceship operator is defined and causing it to defer to the incorrect comparison?

Strictly speaking the comparison operators on that type are broken even in C++17 though, since s1 < s2 is checking a fundamentally different thing than s2 > s1.

qsvui
Aug 23, 2003
some crazy thing

Zopotantor posted:

The */& binds to the object, to declare two pointers you have to write * twice.
(And the common coding guideline that says to only declare one name per declaration only exists because there are people who don't understand that.)

I think that coding guideline is there to discourage you from declaring a bunch of crap at the top of a function like we're still using ANSI C. I just initialize variables closest to their point of use and I've never needed to declare multiple variables in a single line.

Foxfire_
Nov 8, 2010

Xarn posted:

The output is different with C++17 and C++20.
Having a type with a comparison operator that produces one ordering, and an implicit conversion to another type that produces a different ordering is already a "You should feel bad about yourself" situation

redleader
Aug 18, 2005

Engage according to operational parameters

Zopotantor posted:

The */& binds to the object, to declare two pointers you have to write * twice.

this is because c is nothing but a cavalcade of old, bad design choices

QuarkJets
Sep 8, 2008

redleader posted:

this is because c programming is nothing but a cavalcade of old, bad design choices

OddObserver
Apr 3, 2009
Untrue. Programming in general has new bad design choices, too

Phobeste
Apr 9, 2006

never, like, count out Touchdown Tom, man

Athas posted:

Haskell is nominally typed, and doesn't support row polymorphism. You can perhaps encode it in some roundabout way via type classes, but that's cheating.

Oh didn’t know, my bad. Wonder why I thought it was

Volte
Oct 4, 2004

woosh woosh

Phobeste posted:

Oh didn’t know, my bad. Wonder why I thought it was
Maybe you're thinking of OCaml?

Xarn
Jun 26, 2015

Volte posted:

Interesting example. I assume that's because the implicit conversion to const char * is tricking std::pair into thinking the spaceship operator is defined and causing it to defer to the incorrect comparison?

Strictly speaking the comparison operators on that type are broken even in C++17 though, since s1 < s2 is checking a fundamentally different thing than s2 > s1.

Eh, the stdlib guarantees that it will only use the less-than operator, so that type was fine in C++17, if a bit foot-gunny.

Also IIRC that is a reduced repro from QString, so it is not exactly a niche issue :v:

HappyHippo
Nov 19, 2003
Do you have an Air Miles Card?
On one hand, I like char* foo because the type of foo is a pointer to a char, so I feel the * should be part of the type. But then whoever designed c decided that char* foo, boo; is a pointer to a char and a char, which is dumb as hell to me, so I guess putting the * with the variable communicates that.

cheetah7071
Oct 20, 2010

honk honk
College Slice
Declaring a char and a char pointer on the same line feels needlessly confusing no matter how its notated

cheetah7071 fucked around with this message at 20:45 on Sep 28, 2022

FlapYoJacks
Feb 12, 2009

cheetah7071 posted:

Declaring a char and a character pointer on the same line feels needlessly confusing no matter how its notated

Tired:
code:
char* foo
Wired:
code:
 char *foo 
Hired:
code:
char
*foo

Adbot
ADBOT LOVES YOU

HappyHippo
Nov 19, 2003
Do you have an Air Miles Card?

cheetah7071 posted:

Declaring a char and a char pointer on the same line feels needlessly confusing no matter how its notated

Well my preference here would be that char* foo, boo; declares two pointers. But sadly c was not written that way.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply