-
Notifications
You must be signed in to change notification settings - Fork 258
Consider loosening restrictions of Final
#920
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
Comments
A for loop doesn't create a new scope for variables, so if we'd allow fun foo() {
val x = listOf(1, 2, 3).map { i ->
val a = i
a = 10 // error
println(a)
a
}.last()
println(a) // error: a is no longer in scope here
}
I vaguely remember that the reasoning went like this: Since most function arguments aren't assigned to, there was a worry that some people would annotate almost all arguments using Personally I think that annotating local variables whenever possible with |
If the issue is most function arguments are not mutated then I'd be happy to see a Mutable type that means opposite of Final and have function arguments default to meaning Final (with relevant error messages hidden behind a config setting/flag for backwards compatibility). Having a way to minimize functions that are doing mutations and avoid accidental mutations would be nice feature. i think marking almost all arguments as Final would be noisy enough to get readability complaints. |
@JukkaL Thanks for the response!
I disagree, the Kotlin example is contrived to achieve the exact same semantic meaning as the Python code. Created to communicate that despite Python function-scoping the symbol, it still remains semantically consistent with If you were forget about the fact that the same variable is reused for the iteration, and instead think of it as: def foo() -> None:
for i in [1,2,3]:
if "a" in vars():
print(a) # error: runtime okay, but from a type perspective a is not defined yet
a: Final = i * 2 # currently an error
a = 10 # error
print(a)
print(a)
a = 10 # error In this code we can see that within every iteration of the I can see no way in which a
Fair enough, would be nice if the pep had that info in it. I would love a final-by-default setting for function arguments like @hmc-cs-mdrissi proposed. |
I've run into one issue today that makes me wish for final function arguments. A generic type being final can affect what variance could be. List[T] is invariant, but Final List[T] would be safe to have as covariant. I'm aware of Sequence[T] for covariant, but there are libraries that work with lists and not other sequences that never mutate the list and could be marked Final List[T]. tensorflow is one library that has a lot of functions that work on lists/tuples and not other sequence and list invariance complicates annotations a good amount even though it would be safe for it to be covariant as none of the functions mutate the list. This would require TypeVar to also include information about how final affects variance. Something like TypeVar('T', final_covariant=True) could be used for list[T]. Maybe there exists a final_contravariant too, but unsure on an example for it. |
@hmc-cs-mdrissi Perhaps you would want a protocol that matches |
As @KotlinIsland said, |
The problem with Sequence is there are libraries that only work with lists (including isinstance checks occasionally), but never mutate it. That's not possible to describe currently. So my options are lie and use Sequence and let it crash if you pick wrong Sequence type or use list and run into variance issues. My main challenge is this is not my library (tensorflow), but an existing major open source library and it'd likely be difficult to change implementation to support other sequences. A protocol that only matches list - mutation parts I think would work. |
Not exactly what you want though, because you could still send a non- |
This is what use site variance modifiers( fun foo(someList: MutableList<out Number>) {
someList.clear() // valid
someList.add(1.1) // Type mismatch: Inferred type is 'Float' but 'Nothing' was expected
}
val someList: MutableList<Int> = mutableListOf(1, 2, 3)
foo(someList) // no error Here the argument is now covariant, but is still mutable. |
I think that
Final
is a very based idea, but I feel that it's current restrictions make using it painful.I think that split assignments should be allowed:
Most languages allow this pattern to initialize constant variables:
I think these are convenient and enables
Final
to be used in more scenarios, but this is currently an error.But this is allowed:
Another issue I've found is the prohibition of
Final
declarations within loops:This is defined in the pep:
I understand that at runtime the same variable is reused for each iteration of the loop, but if you ignore that fact and just look at the semantic meaning of the code, it matches exactly to this Kotlin example:
Disallowing
Final
in loops doesn't address any of the motivations listed in the pep, any I can't see any reason why it should be disallowed.Also, why are
Final
annotations not allowed on functional arguments?Why? This is commonly seen in other languages. In Kotlin, function parameters can only be
val
(Kotlin's form ofFinal
), in Java a function parameter may be marked withfinal
.The text was updated successfully, but these errors were encountered: