Skip to content
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

Add final keyword #6584

Closed
Simn opened this issue Sep 16, 2017 · 42 comments
Closed

Add final keyword #6584

Simn opened this issue Sep 16, 2017 · 42 comments
Assignees
Milestone

Comments

@Simn
Copy link
Member

Simn commented Sep 16, 2017

  • allow final ident at both field and expression level. Allows assigning to a field exactly once (per branch? probably...)
  • allow final class to forbid extending
  • allow final function to forbid overriding
  • allow that toString thing @back2dos was talking about in the bar
@jdonaldson
Copy link
Member

jdonaldson commented Sep 16, 2017

Glad you're keeping the

@Simn
Copy link
Member Author

Simn commented Sep 16, 2017

Glad you're keeping the

wat?

@piotrpawelczyk
Copy link

piotrpawelczyk commented Sep 17, 2017

Why not e.g. let for final var and then final class/final function?

I'm curious what "that toString thing @back2dos was talking about in the bar" actually is? :)

@jdonaldson pun intended! :D

@ibilon
Copy link
Member

ibilon commented Sep 17, 2017

let doesn't convey any meaning,
final was chosen instead of const since this is about reference not mutability (if I'm not mistaken)

@delahee
Copy link
Contributor

delahee commented Sep 17, 2017

On a personal level, and having experience Const Hell In cpp, I can't say I am thrilled about this.

As long as we don't have it in parameters and return types, it's ok to me.

@Simn
Copy link
Member Author

Simn commented Sep 17, 2017

It's about references, not types.

@delahee
Copy link
Contributor

delahee commented Sep 17, 2017

Ok to me then!

@piotrpawelczyk
Copy link

piotrpawelczyk commented Sep 17, 2017

@ibilon

let doesn't convey any meaning,

Well, I would argue about that. It certainly has a meaning! See https://stackoverflow.com/questions/37916940/why-was-the-name-let-chosen-for-block-scoped-variable-declarations-in-javascria.

I'm not a kind of a person who fights for every additional character typed on a keyboard but since this is a completely new keyword/feature, why not make it concise from the get-go?

Whatever you choose, please have it already! :)

Btw. there's also Scala's val. :)

@elsassph
Copy link
Contributor

I'm really used to 'const' everywhere in JS (with the same semantics) excepted when mutable. But I'm not looking forward to adding 'final everywhere :(

@elsassph
Copy link
Contributor

I don't want to add a keyword before 'var'. I don't even care about making classes and functions non-overridable. I'm not looking to coding in Java.

Sorry for being negative though, it's still progress to come.

@RealyUniqueName
Copy link
Member

I think final is a good name for classes and methods. And it has the same meaning in other languages: inheritance is limited.
But final for variables is a bad choice. It has nothing to do with inheritance. I prefer const instead final var.

@Simn
Copy link
Member Author

Simn commented Sep 17, 2017

Actually what we discussed was final x. I think...

@RealyUniqueName
Copy link
Member

That's a bit better, but still has different meanings for classes/methods and vars.

@ibilon
Copy link
Member

ibilon commented Sep 17, 2017

For me seeing const a = [0]; means immutability, so no push, (because I'm used to c++'s const I guess) which wouldn't be the case.
So I'm in favor of final.

@elsassph
Copy link
Contributor

Can we say that objecting that it can confuse C++ devs is moot?
For the sake of understanding the point I'm reading about C++ consts and I'm crying.

const behaving as final var will not confuse anyone with ES6/TS, PHP or ActionScript experience where it behaves the same - and Haxe is closer to these languages than C++.

final x feels really odd... if you want to avoid const absolutely then maybe use let as in Swift and other ML languages.

@Simn
Copy link
Member Author

Simn commented Sep 17, 2017

let was on the table, but I find it quite confusing too. It means completely different things in OCaml and JavaScript, and would have yet another meaning in Haxe.

const a = [0] just looks completely wrong to me for reasons @ibilon mentioned.

@elsassph
Copy link
Contributor

const a = [0] looks just fine to me - that's a const reference to a dynamic array object.

@vizanto
Copy link

vizanto commented Sep 18, 2017

In Scala there's var for mutable references and val for immutable references.

@waneck
Copy link
Member

waneck commented Sep 18, 2017

Can't we all agree that final conveys what we want without it being confusing?

@ncannasse
Copy link
Member

ncannasse commented Sep 18, 2017 via email

@Justinfront
Copy link
Contributor

Justinfront commented Sep 18, 2017 via email

@Simn
Copy link
Member Author

Simn commented Sep 18, 2017

I don't like val because it looks like var and doesn't mean anything.

We can find pros and cons for any suggestions, so I'll just go with the original one. Can't make everyone happy anyway. You all will be happy once you realize we can do final countdown; // dadadaaadaaa

@wiggin77
Copy link

Currently it is very difficult to get Haxe generated code fully thread safe on Java and C# targets, specifically around var initialization within constructors. If this proposal means vars with 'final' are expressed in Java and C# targets with their language equivalents I believe we would finally have the tools needed to make truly thread safe code without crazy hacks. Not sure if that is the intention.

@jdonaldson
Copy link
Member

As a side note, it would be trivial to make "val" look distinct from "var" via code highlighting rules in an editor.

@nadako
Copy link
Member

nadako commented Sep 18, 2017

I don't really see much benefit from having final locals in general, but I also think val is a bit nicer. final is pretty clear as well, const is quite meh, I agree that it's confusing wrt object immutability, which we might want to add someday too.

Final class fields would cover a lot of cases where you'd want to have (default,null) property for an immutable object with a much nicer syntax, which is awesome! Still I'd like to have some generic shortcut for internally-mutable default,null fields as well.

As for the classes and functions, I would suggest having it inverted: everything is non-overridable and you can mark things explicitly overridable. I think that's a bit cleaner and stricter, but I don't have a strong opinion on that (and I know that this is unlikely to be changed as it's too breaking).

On a related note, I think we should have an optimization filter that makes classes/functions final if they aren't overrided in this compilation (configurable by some flag, ofc). This is good for performance, I think (saving vtable lookups without any explicit syntax).

@Simn
Copy link
Member Author

Simn commented Sep 18, 2017

As for the classes and functions, I would suggest having it inverted: everything is non-overridable and you can mark things explicitly overridable.

No

On a related note, I think we should have an optimization filter that makes classes/functions final if they aren't overrided

Yes

@vizanto
Copy link

vizanto commented Sep 19, 2017

Uhm, var doesn't mean anything either.
Unless it's a shorthand for:

var = variable
val = value (real values are not mutable)

Code lining up and having the best option (val) be just as short as the more error prone one (var) is a plus.

Never had any problems distinguishing both in scala code ... and this was 6 years ago already.
As a side note, it looks even nicer in scala because functions are defined by def:

def something : Int = 1
var someother = 2
val someaethe = 3

@vizanto
Copy link

vizanto commented Sep 19, 2017

But allowing final someaethe = 3 would be my 2nd choice and is allowed in Java. final var would be even more verbose than java. That should make you cringe.

@Simn
Copy link
Member Author

Simn commented Sep 19, 2017

What if I told you, typing fi<tab> is gonna give you final in any decent IDE. Typing va<tab> will make you sad.

@piotrpawelczyk
Copy link

It won't because va<tab> is three letter word as much as val so you'd not need an IDE help for this one, while you'd kind of have to use fi<tab> since it's a looooooot shorter than final (whole 2 key-strokes!). :D

Btw. is it possible to start working on this feature and decide on a keyword only when it's being merged into the main branch? I mean it's a bit pointless arguing about a keyword while I'm sure more interesting problems will emerge when implementing it. :)

Btw. no 2: @back2dos what was that toString thing you were talking about in the bar? Why am I the only one curious? :)

@Simn
Copy link
Member Author

Simn commented Sep 19, 2017

It won't because va is three letter word as much as val so you'd not need an IDE help for this one, while you'd kind of have to use fi since it's a looooooot shorter than final (whole 2 key-strokes!). :D

Teach your IDE to insert a space when auto completing fi<tab> and you actually save a keystroke. Come at me keystroke people. :P

@back2dos
Copy link
Member

back2dos commented Sep 19, 2017

Because y'all just love it when I go on lengthy rants, I prepared a very special piece for you titled:

I want to be able to name stuff and in 2017 Haxe does not provide a proper way to do that

In opening, a few words chosen with utmost care to depict as accurately as I can my view regarding the status quo in this particular matter: it's an effing disgrace.

I don't know how you cannot see that something so fundamental is just entirely broken, so please excuse me if I'm stating the obvious at any point. I simply cannot figure out which part of this escapes you.

Naming should be the simplest thing to do in a programming language, and in Haxe it's impossible to do for values. And no, declaring a variable is not the same as actually naming a value. For those who don't understand such a simple notion, let me exemplify it: you cannot assign a different type to a typedef, you can assign a different value to a variable. There you go. Ain't no rocket science.

Extending the matter a little further, it should be trivial to:

  • name a value, without inevitably declaring a mutable variable
  • compound values, without having to always resort to heap-allocated mutable objects (that have readonly fields at best) which either come with a full-blown class or wacky dynamic semantics like the ability to add fields per reflection

One way to achieve this would be to have "something like" standard ML's val for the former and anonymous records for the latter. I don't really care about the specifics. What I will say is that naming something should not be more verbose than declaring a variable and storing that something in it. Naming is simpler than a variable declaration (which actually does two things) and the syntax should at the very least not reverse that relationship.

(I'm basically done here. What follows is only as relevant as are the weird twists and turns this discussion is taking. Feel free to ignore it.)


On a side note: it's idiotic that all of us should pay the price for questionable design decisions in C++, just because some of us have apparently suffered horrible trauma from it. I feel very sorry for you and all that, but I don't see why we should all be made to pay for it indefinitely. In the words of Dijkstra:

Perfecting oneself is as much unlearning as it is learning.

There may be good reasons not to use const (such as let being a better choice IMO), but C++ messing something up should not ever be a reason for anything. Ever.


On another side note: final is just weird. Seriously.


On yet another side note: val is not distinct enough. I wanna gloss over code and immediately see the moving parts. Having to distinguish val and var is not helpful (it doesn't get better if you look at a diff without syntax highlighting). The choice arguably has some poetic beauty, but definitely isn't pragmatic. I do get that alignment thing, but def or let or anything else would do just fine.


About the "toString thing from the bar": the broader the type, the less one should write (I require less, so I specify less). You will see that this largely holds in Haxe already: {} is definitely broader and thus shorter to write than { foo:Int }. However, for fields it breaks apart:

class Test {
    static function print(o:{ toString:Void->String }) 
        trace(o.toString());
    static function main() 
        print("Haxe is great!");//will not compile for totally obvious reasons
}

It should be really easy to describe the structure of something I wish to consume, but not mutate. If I have no intention of mutating the argument's value, I must go out of my way to specify it per { var toString(default, null):Void->String; }. For function fields you can actually use function field syntax, so let's pretend that's a non-issue. Still, for data it is just painful of having to write twice the code to achieve half of the effect of the default.

To summarize: a readonly type is a broader type than its readwrite counterpart (as evidenced by variance) and the broader the type, the less one should write. I'll let everyone draw their own conclusion.


There is something to be said about the future of computing being parallel and therefore immutability becoming very important. But really, I don't care about the use cases, as much as I care about one simple thing: reducing cognitive burden. When I see an identifier being declared and I can assume it won't change, then that's great. It's just so much easier to reason about the code. I don't want to keep stuff in my head. That's what I use a compiler for.

Example: when I refer to Simn, I want to know that I get that particular guy. There's a very real risk that his mood degrades between the two points in time that I retrieve the value, because of me annoying him too much in between, but it's going to be the very same Simn. Not a bearded Simn from an evil parallel universe or anything else. That's one less thing to worry about. And trust me, if you have projects in production for +5 years or quite simply have a team of +15 haxers, then all these benign problems do add up to a death by 1000 cuts.

Similarly I would argue that function arguments and variables captured in pattern matching should not be mutable - interestingly for loop variables we can enforce such a thing and rest assured, that's not because it's impossible make mutable loop variables without breaking the loop - in fact tink_lang does just that (only because it cannot actually generate immutable vars when it needs to). If you need to sanitize the function argument, you can shadow it by a variable holding the sanitized value. And so on. It could all be made to work. I'm sure that's not gonna happen anyway, but I'm saying that would be the sane thing to do. As would be to follow Dan's idea that was honored with a "no".

Rest assured: Haxe has things backwards on that subject and it matters. People are doing quite desperate things to escape this madness.


All this may easily seem like pointless bitching if you either don't use Haxe in production or you have your little indie studio with barely a hand full of haxers of whom at least one is a freaking genius who by the way can just change the language whenever he pleases. I can only hope that this is not the intended way to use Haxe out in the field. If so, we should include an adequate warning.

There's all this talk about simplicity, and we can't even have the simplest things. Please change that. Thanks.

@ncannasse
Copy link
Member

All this may easily seem like pointless bitching

👍

@back2dos
Copy link
Member

You know, I really like the part where you try to be smart and it hinges on you quoting part of what I say out of context. It's not only an admirable display of intellectual prowess and a great example of how to participate in argument. Much rather it stands as an example of the type of leadership Haxe needs and I can already see how it will make selling the technology to more companies much easier. You should really be proud of yourself. Bravo!

@ncannasse
Copy link
Member

Hey Juraj, don't be offended and I apologize if you felt this way.
We just came back from the conference were we had very nice talks together, so please take it as an humorous comment on my side and not as something else.
I'll answer more about the actual feature and the discussion that comes with it later when i have time available.

@damoebius
Copy link
Contributor

So what ? Should we vote between final and const ?
Final is cool because we can use it for class, function, and var. But honestly it's quite useless for me, i never need it.
const is just to replace "static inline var" by something more readable, more harmonious.
"final var" looks more verbose than const. But it's also acceptable.

@back2dos
Copy link
Member

My understanding is that what is being proposed (and has been merged) is final rather than final var.

@AdrianV
Copy link
Contributor

AdrianV commented Sep 23, 2017

I think that val is a bad choice for a new keyword, since it is used a lot in existing code as an identifier already. final or let may break less.

@mastef
Copy link
Contributor

mastef commented Nov 29, 2017

You can't redeclare a class method function something unless it's defined as dynamic function something - you'll get the error Cannot rebind this method : please use 'dynamic' before method declaration

So should you be able to redeclare a var if it wasn't defined as dynamic var? Or should you get the error Cannot rebind this var : please use 'dynamic' before property declaration? 😅

I like how in Golang you can't redeclare vars in the same context easily, and how it's a nice way to catch many beginner mistakes and reduce unreadable code. This holds especially true when going through a file that has the same property name defined in the class, in multiple method calls, and in block contexts 4+ times - making debugging of it super-hard...

@vizanto
Copy link

vizanto commented Dec 2, 2017

So should you be able to redeclare a var if it wasn't defined as dynamic var?

+ 💯 for semantic symmetry

Too bad variable means "Likely to change or vary; subject to variation; changeable"

@mastef
Copy link
Contributor

mastef commented Dec 3, 2017

I guess what should be noted in the docs - since it might be unexpected for js users of const or php users of define() - is that final can be shadowed. And it can also be changed through Reflection. ( use cases java users are used to with final - and behaviour that changed over the years ).

class Main {
	static final a : Int = 123;
	static function main() {
		trace(a); // 123
		Reflect.setProperty(Main, "a", 124);
		trace(a); // 124
		var a = 125;
		trace(a); // 125
	}
}

@Simn Simn added this to the Backlog milestone Apr 18, 2018
@Simn Simn modified the milestones: Backlog, Release 4.0 May 8, 2018
Simn added a commit to Simn/haxe that referenced this issue Sep 4, 2018
Simn added a commit that referenced this issue Sep 4, 2018
* support `final class`

see #6584

* fix parsing so it allows both `extern final` and `final extern`

Also hold back on the warnings because the Flash API has `@:final` everywhere

* properly represent final fields as `cf_final`

* add missing isExtern on ClassField

* fix extends display handling of final classes

* ctrl s

* annoying

* don't require inits for extern final fields

* don't check for final inits on interfaces

* don't lose final on structure type fields
@Simn Simn self-assigned this Sep 5, 2018
@Simn
Copy link
Member Author

Simn commented Sep 5, 2018

final at expression level is still missing.

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