Skip to content

Discussion: Optional declarations and syntax sugar #27

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
tatewake opened this issue Sep 28, 2022 · 12 comments
Closed

Discussion: Optional declarations and syntax sugar #27

tatewake opened this issue Sep 28, 2022 · 12 comments
Assignees

Comments

@tatewake
Copy link

So based on what I understand about cppfront and Cpp2 as it stands, there's a union keyword that effectively infers / uses an std::variant wherever it's used, and it's invisible to the end-user-- I think that's great and a good choice.

However, it looks like when using optionals, you still have to declare them with the original C++ syntax… I think there's a missed opportunity here that has been well treaded in C# (via "Nullables"), JavaScript / TypeScript, and Swift.

If you look at Swift's Optionals, we have declaration like this:

var a: Int	// int type
var b: Int?	// Optional Int type
var c: Dog?	// Optional class type

And accessing values are like this (the last two lines are called "optional chaining" in most languages):

print(b ?? 7)		// prints the value of "b" if non-optional, otherwise prints "7"
let h = c?.height	// accessing member variables from an optional class
c?.bark()		// calls "bark" method on c if c is not null, does nothing otherwise
d?.data?.dog?.bark()	// Similar to above, but you have a hierarchy of optional members

Swift also provides the concept of "unwrapping" an optional; I don't want to muck up the above with this, but it would be negligent of me not to mention it:

if var c = c {	// try to "unwrap" the optional into a non-optional type
	c.bark()	// optional was successfully "unwrapped", c is now a non-optional type, and "bark" method can be called
}

In my opinion, Swift is a very "Optional first" type of language, meaning you often want to see if something "can be" an optional before you even consider a non-optional type, and I think this kind of thinking might also benefit C++… and maybe a good vector to do this would be through Cpp2 / cppfront.

If for nothing else the syntax sugar of using a declaration of type? to imply std::optional<type> and providing support for optional chaining (the c?.bark() instead of writing if (*c) { c->bark(); } would be tremendous in my opinion.

One other thing to consider about Optionals in general (as well as how they currently stand in Cpp2), is that the absence of a value is typically represented as null or nil or (in the case of C++)nullopt. I do agree (and totally support) the idea of eliminating NULL in Cpp2, but I think they track you'll find here is similar to Swift, where nil (their version of null) only exists to support the "absense of a value in an optional."

Whether or not you wanted to go down a similar "unwrap" path would be up to you (and dependent on if you even wanted to go down this "syntax sugar" idea or not), but I think there's lots to learn from other languages that do optionals that C++ can't do without considerable effort that are worth considering here.

tl;dr:

  • Consider treating optional types like you treat union -> std::variant
  • Learn from other languages (C#, TypeScript, Swift), and provide syntax sugar:
    • For declaration: b: Int?
    • For chaining: c?.bark()
    • For defaults: b ?? 5
  • Consider having a "null" but only for optional types (nullopt_t is okay, but maybe there's a better long-term naming solution)
  • Consider advanced ideas like optional unwrapping from Swift
@dkaszews
Copy link

I like the idea of T? as shorthand for std::optional<T> like in C#, and would also propose algebraic types like in Haskell: T|U|V for std::variant<T, U, V> and T&U&V for std::tuple<T, U, V>.

The thing is, I'm not sure if it's achievable without breaking the goal of context-free grammar. x? is a valid type when x is also a type, but a broken half of a ternary when it is a variable. If my proposition for algebraic types work, we may instead make a special case that T|_ is treated as an optional, as it can be read as std::variant<T, nullopt_t>.

My additional concern is how to make those extensions work when the user cannot use the compiler-provided stdlib. I work at a kernel driver AG work, so instead of stdlib with its global allocators and system calls, we use our own implementation. Embedded folks will bd in similar situation. How to let Cpp2 know that T? should not be expanded to std::optional<T> but kernel_std::optional<T> instead?

@dkaszews
Copy link

dkaszews commented Sep 28, 2022

Regarding the null propagation operator ?., I find it a feature that could really simplify a lot of code, especially if you have multiple levels:

if (!a) return nullptr;
auto b = a->getB();
if (!b) return nullptr;
auto c = b->getC();
if (!c) return nullptr;
return c->foo();

// becomes
return a?->getB()?->getC()?->foo();

The issue I always see cited is that while flattening nulls is an obvious thing to do for pointers (or references in other languages), it is not so clear cut for optional, as C++ treats optional<optional<T>> as a completely different type to optional<T>. And it needs to do it, to e.g. facilitate generic code which wraps and unwraps into optionals, objects which may be optional themselves. Therefore, you may run into an issue:

int Object::foo();
obj?->foo();  // returns optional<int>

optional<int> Object::bar();
obj->?bar();  // returns optional<int> or optional<optional<int>>? 

Object Object::parent();
optional<Object> Object::next();
obj?->next()?->parent()?->bar(); // returns what? 

@jcanizales
Copy link

How to let Cpp2 know that T? should not be expanded to std::optional<T> but kernel_std::optional<T> instead?

I would think this isn't a problem this layer should be concerned with, but a layer below it. You also might have to deal with third-party C++ code that uses std::optional, and the solution is the same for both cases: Replace the standard library with your own implementation when compiling that code.

@redradist
Copy link

redradist commented Sep 29, 2022

@dkaszews

The thing is, I'm not sure if it's achievable without breaking the goal of context-free grammar

I am not sure why Herb choose Context-Free grammar, because everything less or more interesting always contains Not Context-Free grammar.

Also having C++ naming style for containers with < and >: std::optional<int>, it is already not Context-Free grammar, because semantic of < and > depends on context

@JohelEGP
Copy link
Contributor

The answer lies in the CppCon 2022 link at https://github.com/hsutter/cppfront#wheres-the-documentation.

@hsutter
Copy link
Owner

hsutter commented Oct 2, 2022

If for nothing else the syntax sugar of using a declaration of type? to imply std::optional<type> and providing support for optional chaining (the c?.bark() instead of writing if (*c) { c->bark(); } would be tremendous in my opinion.

Do we have data that this is what we encourage C++ developers to do today by hand? I'm a fan of making default the things we already teach. See this 1-min clip from the talk.

I'm especially wary of the C# treatment of optional and null, because for years now I've been hearing from the C# design team that plumbing nullability through the C# language cost more than it was worth. We should learn from the experience of other languages, not only what they did but how it worked out for them and whether they would do it again. (I don't know if that experience about language support for nullability was the same as their language support for optionality.)

@hsutter hsutter closed this as completed Oct 2, 2022
@hsutter hsutter self-assigned this Oct 2, 2022
@tatewake
Copy link
Author

tatewake commented Oct 2, 2022

Do we have data that this is what we encourage C++ developers to do today by hand?
I'm a fan of making default the things we already teach.

I'm not sure what "we teach" C++ developers about std::optional. When I've met C++ developers (either fresh or more experienced) in the last few years, very few have used either std::variant or std::optional let alone even know what they are used for.

I'm especially wary of the C# treatment of optional and null, because for years now I've been hearing from the C# design team that plumbing nullability through the C# language cost more than it was worth.

Well, I get that back-porting a feature into a well-established language can be a potential nightmare, but Cpp2 isn't really "there" yet, right? Also, considering you're just doing code generation at this point, and not writing a Cpp2 compiler, I'd imagine the cost of doing experiments like these are also pretty low?

We should learn from the experience of other languages, not only what they did but how it worked out for them and whether they would do it again.

Right… and why I brought up Swift-- to me it does optionals right, and to your point I think they'd definitely do it the same way again. C# was mentioned for the sake of those reading that were not familiar with Swift.

I did watch the entire talk, and I do believe I understand your motivation being something like "to have a language that enforces the things we should be doing with C++." I'm excited to see where you go with it. (Particularly with classes, generics / templates, const correctness, and maybe--fingers crossed-- optionals.)

When I saw the union implies std::variant part of your talk, I said to myself-- "Aha, syntactic sugar!"
Whether that was your intent or not, I think it does qualify as such, even if it might be "a fluke" it could actually serve as "an opportunity" to see what else could be sugar'd up to be more readable / writable / approachable.

@JohelEGP
Copy link
Contributor

JohelEGP commented Oct 2, 2022

There might be some overlap with the work for "throwing values".

@jcanizales
Copy link

Right… and why I brought up Swift-- to me it does optionals right, and to your point I think they'd definitely do it the same way again.

Maybe @lattner would like to confirm or deny. He's usually responsive.

@lattner
Copy link

lattner commented Oct 2, 2022

I'm not sure what the specific question is, but yes, I'm pretty happy with the design of optionals in swift. The one thing I'd do different is axe if let x = being an implicit optional unwrap. It would be more orthogonal to make it explicit as if let x? = ... which would define away the if case nonsense etc.

@nigeltao
Copy link
Contributor

nigeltao commented Sep 7, 2023

I'm especially wary of the C# treatment of optional and null, because for years now I've been hearing from the C# design team that plumbing nullability through the C# language cost more than it was worth.

Can you elaborate (even if only passing on second-hand knowledge) on what the (unexpected?) costs were?

@hsutter
Copy link
Owner

hsutter commented Sep 10, 2023

As I understood it, it was mainly because it ended up being viral throughout the language specification... many language features ended up with special logic to account for nulls.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants