Fantom does well at stamping out bugs that occur again and again via language features. This is Great, so I'd like to propose another repetitive bug to remove:
String str = getStringFromSomewhere()
str.upper()
if (str == "EXIT") System.exit(0)
Of course, this code doesn't work as expected, because we forgot to assign the result of str.upper() back to the variable. This bug is one we've probably all done.
Given Fantom has true immutable classes, this is likely to be a more common bug in Fantom.
The proposal is to add a facet, or other feature, that forces the return value from a method to be used by the caller:
@MustUseReturn
Str upper() { ... }
String str = getStringFromSomewhere()
str.upper() // won't compile as return value not used
I hope that the bug-squashing power of this is obvious :-)
tacticsThu 7 Jan 2010
The case I always see is sort. Every time I learn a new language, I have to go to the docs to look up whether or not sorting is in-place in the language.
I would rather see it issue a warning instead of an error, though.
casperbangThu 7 Jan 2010
That would be a great feature, but I agree with tactics that it should be a warning. The Java compiler has shown us that cranking static checking too high up in the compiler just gets annoying. We all know production code should compile without warnings anyway no?!
heliumThu 7 Jan 2010
What is the more common case? That the return value has to be used or that it's only optional? Depending on the answer to that question maybe this behavior should be the default for all methods that have return types other than Void and you'd explicitly mark methods with @OptionalReturn.
jodastephenThu 7 Jan 2010
I would rather see it issue a warning instead of an error, though.
Warnings are evil, and just ignored, because you get too many of them. You also need an ignore warnings element. Please lets not add warnings to Fantom.
What is the more common case? That the return value has to be used or that it's only optional?
Possibly. But as an error, I think it wants to be the way I suggested, and only applicable for idempotent, safe methods that return a new instance.
brianFri 8 Jan 2010
What is the more common case? That the return value has to be used or that it's only optional?
Since most APIs are designed to return This, the common case is by far that you don't need to use the return value.
So this absolutely has to be an opt-in feature rather than an opt-out feature.
The proposal is to add a facet, or other feature, that forces the return value from a method to be used by the caller:
I think the basic idea is really good. For example we already trap special cases as not statements. For example if you try to use a field getter as a complete statement today it is a compiler error.
So I am board with supporting the feature and making it an error (versus a warning).
The question is what is the best to think about the problem?
The way I think about the problem is deeper than just an annotation. A method which doesn't produce a side effect is "pure" from a functional perspective. Knowing that a method is pure has all sorts advantages for potential compiler optimizing and "safe scripting". One those advantages is that you must use the return value. There is no point in calling a pure function without using its result (no more so than calling a field accessor).
So what is the best way to annotate a "pure" method? I'm thinking the const keyword might be the right solution to jive with the rest of the language.
But does that put restrictions on what you do inside the method?
heliumFri 8 Jan 2010
The easy way would be to say that pure function can only call other pure functions. That makes sure that the function is truly pure, but might be to restrictive.
And while it's true that it doesn't make sense to call pure functions without using their results these are not the only functions. You don't call File.readAllStr or Range.random just to ignore the result but those functions are definitely not pure.
tacticsFri 8 Jan 2010
I had an idea for a language feature a while ago which was essentially "purity inference." You mark primitive functions as pure or impure, and methods built on top of only pure function inherit their purity. So an echo or logging would be impure, but math methods, but string manipulation would all be pure.
I never bothered to work out the details, but the idea always seemed kind of neat. Maybe it fits here. It the compiler could infer purity instead of having to explicitly mark it, that'd reduce code clutter. Then, if you want to override the default, you could use @pure (for when you want to echo-debug a pure method or whatever).
heliumFri 8 Jan 2010
Disciple has an effect systems with full inference, but I'm not sure this fits to Fantom.
IIRC D has a simple pure annotation. I think that is more like what we might see in Fantom.
Some edge cases with inference: All virtual or abstract methods could be overridden in an impure way so they had to be considered impure. Anything that uses the FFI would have to be considered impure. I don't know if this might ever be a problem, but there might be a situation where you think one of your methods should be pure but the compiler infers that it's not and then you have to walk down recursively all the methods that this methods calls to find the root of the problem.
brianFri 8 Jan 2010
And while it's true that it doesn't make sense to call pure functions without using their results these are not the only functions.
True, but I think these are exactly the same cases where you probably just want a warning, versus a full blown error right? I think of it as language restrictions versus more of a "Fantom Lint".
D has a simple pure annotation. I think that is more like what we might see in Fantom.
I was thinking of D's model myself.
All virtual or abstract methods could be overridden in an impure way so they had to be considered impure
I think it just requires subclasses to maintain the contract, so they must be pure too.
anything that uses the FFI would have to be considered impure.
Very true, this is just like we have to assume every FFI call is nullable. Although eventually databases which annotate Java/C# methods as pure or non-nullable could eventually be used.
DanielFathFri 8 Jan 2010
A side note why wouldn't
String str = getStringFromSomewhere()
if (str.upper == "EXIT") System.exit(0)
work? Is it improtant to have that str=str.upper?
heliumFri 8 Jan 2010
jodastephen thought for whatever reason that upper is a method that mutates the string itself instead of returning a new string.
It's not important to write str = str.upper and your example is perfectly fine.
katoxFri 8 Jan 2010
The easy way would be to say that pure function can only call other pure functions. That makes sure that the function is truly pure, but might be to restrictive.
This sounds good on paper but it doesn't really scale. Try to make a const method in C++ - there is always something what prevents you to do it. Plus trying to workaround this by @ItIsPureCallBelieveMe makes things even worse.
@pure could be a nice performace booster too but I don't think it is usable enough to be worth all the hassle.
heliumFri 8 Jan 2010
Try to make a const method in C++ - there is always something what prevents you to do it.
Third party code that is not const-correct. Other than that I never had problems. But I'm not really sure that this is on-topic.
jodastephenSat 9 Jan 2010
I think that the discussion demonstrates ow easy it is to get sucked into FP like solutions, and that isn't Fantoms area.
My proposal is for something (facet/keyword) that a user adds that forces use of the return type. While I'm OK to see that extended to so-called pure functions, I think terminology becomes important. In other words, Fantom mustn't use FP terminology or wording when describing the feature.
I'd also not thought about the compile enforcing the facet/keyword wrt to the code called internally. That seems overly restrictive in a looser language like Fantom. I see this feature as removing bugs for the method caller, not constraining the method writer.
Oh, and,
String str = getStringFromSomewhere()
if (str.upper() == "EXIT") System.exit(0)
should compile, because the upper() method result is being used.
DanielFathSat 9 Jan 2010
I like joda's reasoning. Making things pure seems like such a hassle. Of course you could wrap all unpure code in pure wrappers.
andySun 10 Jan 2010
Interesting discussion, and certainly valuable. But on the surface, this seems like a feature thats better implemented in an IDE.
Marking methods as side-effect free is interesting, though I'm not sure how valuable/applicable that would be in a language like Fantom. I think we have the correct focus now - immutable data structures and no shared state b/w threads.
tcolarTue 12 Jan 2010
I think this discussion and others are turning a little bit into :Function programming or not".
Personally I really like Fantom approach of picking best features of both world without being a "my way or the highway" language like a lot of the other newer language seem to be.
A programming language is a tool, I want it to be flexible and easy to use.
On the particular matter I think it's a bit over the top, could be a facet I guess.
brianThu 21 Jan 2010
I think we should continue this discussion, because it is a valuable idea - especially in conjunction with changes to List.sort to return a new value.
I think this is a fairly easy feature to implement and provides a lot of bang for the buck.
But the key issue for me is naming the facet. Ideas:
@pure
@mustUseReturn
@idempotent
@invariant
@notStmt
@noSideEffects
use const keyword
None of those really work for me - ideally I'd like a simple, single word that isn't as limited as "pure" and not as obscure as "idempotent".
DanielFathFri 22 Jan 2010
I'm curious, const seems ideal. It isn't limited as @pure and and isn't as obscure as @idempotent.
katoxFri 22 Jan 2010
I got lost what is the current proposal - the discussion diverged a lot from the first post.
If it is about functions with no side effects then const keyword seems to be fine. This would however need someone to go through all core API and mark everything accordingly.
One of the things I don't like on pure/const approach is impossiblity of creating transparent caching classes (later on) - you'd have to refactor all classes that use the code (not only inherit from).
The other thing are wrappers which could "correct" unpure method and functions to pure. I don't like that at all. It feels like casting immutable to mutable and forcing changes anyway. In the end it guarantees you nothing (but could induce hideous issues).
Having pure things is very natural when immutability is the default - which is not the case of Fantom.
brianFri 22 Jan 2010
I don't want to turn this feature into a full blown pure/const function type system.
I'd prefer to focus on the more manageable task of what Stephen originally proposed: indicating that a method doesn't do anything, and that if you don't use the result then you shouldn't bother calling the method.
However I don't think the right semantics if mustUseReturn, I think it is more valuable to nail down something a little more specific about the method that might be generally useful - such as @idempotent.
DanielFathFri 22 Jan 2010
@non-volatile (or @volatile if it is the less common case ) perhaps?
andyFri 22 Jan 2010
I don't want to turn this feature into a full blown pure/const function type system.
I'd prefer to focus on the more manageable task of what Stephen originally proposed: indicating that a method doesn't do anything, and that if you don't use the result then you shouldn't bother calling the method.
I don't see how those are different. The only value in this feature is making the compiler verify you have not modified any state in your method. That screams keyword to me personally, and while I don't like overloading the const term, it seems most appropriate here.
brianFri 22 Jan 2010
The only value in this feature is making the compiler verify you have not modified any state in your method.
That isn't the only value - that is actually a very different problem than the one Stephen was proposing. What Stephen was proposing is to solve was bugs in the caller of the method - that a caller to method like List.sort must use the return value or else it is a bug.
The flip side is checking for the person actually writing the method that if they declare it as "pure" then the compiler verifies nothing other than pure calls are used. But that is an entirely different problem and a huge feature because it gets into all sorts of thorny issues like ability to use reflection, dynamic invoke, etc.
So I think the simple low hanging fruit here is just marking a method which provides error checking for the method caller, not the method implementator.
andyFri 22 Jan 2010
I realize that, and that is why I have a pretty big objection to this. Because the root issue is const methods. In which case, you get this "must use return value" for free.
The List.sort is a documentation issue. If you don't document how a method behaves, you're probably not going to add the facet either, so we end up with a half-ass system here. I think this oversteps the compilers duties given how it will be used/implemented.
I feel pretty strongly we solve const methods, or do nothing.
jodastephenFri 22 Jan 2010
I think we all agree that a method like List.sort should document how it behaves in order to be usable. The question is whether this is just words, or more than that.
I would place this in a similar boat to null specification. In Java, most APIs don't specify whether each parameter does or does not accept null, and the same with the return type. However, for my $work, we have a convention whereby each method specifically states the null behaviour in Javadoc. (These days, this could be an annotation). For Fantom, we eliminated this silliness, by having null in the type system.
The same applies here. No matter what, the information should be documented. But why just document it in text, when it could be machine readable? (That the machine readable approach allows the "no expression" behaviour of the original request is almost a side benefit).
And Andy, you are right to say that users might not document the methods with the annotation/keyword. But thats equally true of any documentation, as List.sort shows. If you can think of this as just formalised documentation, with a nice side benefit, perhaps I might persuade you as to its usefulness.
BTW, I agree that a full solution is locked down const methods, with detailed checking by the compiler as to the truth of the pureness of the method. Such an approach would be entirely appropriate in a language like Scala which uses the compiler as a hammer to attack every problem.
But, Fantom isn't Scala. Its the middle ground language between extreme typing (Scala) and little typing (Ruby). It uses just enough type information to catch the main problems that occur without actually getting in the way excessively.
In summary: Full pureness checking is entirely at odds with Fantom-style. Avoiding common programmer errors and moving documentation to machine readable is entirely in line with Fantom-style.
brianFri 22 Jan 2010
In summary: Full pureness checking is entirely at odds with Fantom-style. Avoiding common programmer errors and moving documentation to machine readable is entirely in line with Fantom-style.
I am in agreement. I don't think full pure function checking makes sense in Fantom. But I think if we go to the trouble of documenting that a method is idempotent (which I do in List and Map), then might as well use a facet so that it is machine readable and enables extra error checking (be it in the core compiler or in a compiler add-on).
andyFri 22 Jan 2010
I don't think full pure function checking makes sense in Fantom
I completely agree; re my original response. My objection was enforcing this constraint without actually verifying that the code is indeed idempotent seems fragile.
So my stance is:
No pure method support in Fantom
I like having an @indepotment facet
I do not think the compiler should enforce any use of it
Seems handy for tool and IDE support however
jodastephenFri 22 Jan 2010
OK, so we agree on points 1, 2 and effectively 4 too. The only question is whether "handy for tool support" should include the compiler, as per the original request of the thread.
I struggle to understand why Fantom shouldn't, at the compiler level, use the information at the call-site to eliminate a whole class of bugs. That seems eminently sensible and developer friendly.
BTW, I agree that the IDEs cuold do it, but why should they need to do the work when it can be a core feature?
Anyway, an @idempotent facet or similar keyword would be a great first step.
casperbangFri 22 Jan 2010
BTW, I agree that the IDEs cuold do it, but why should they need to do the work when it can be a core feature?
Sort of like checked exceptions became a core feature? Hmm.
jodastephenFri 22 Jan 2010
Huh? I don't think there is any connection with checked exceptions.
If you don't use the result of an idempotent method its a guaranteed bug. Exception catching is way more complex and subtle than that.
andyFri 22 Jan 2010
I struggle to understand why Fantom shouldn't, at the compiler level, use the information at the call-site to eliminate a whole class of bugs. That seems eminently sensible and developer friendly.
Because by making the compiler enforce it, it is no longer a documentation feature, it is a language feature, and there is no way to guarantee it actually works as advertised. It will be taken for granted, and is ripe for a new class of bugs, where expected behavior is not guaranteed.
So, lets remove the "idempotent-ancy" from the equation, and go back to just @mustUseReturn. We are saying the compiler is forcing me to use result of this method. I'm on the fence with that, but feel like without the side-effect free guarentee, this seems of little value. But I wouldn't hold it up if everyone else likes it.
katoxFri 22 Jan 2010
If you don't use the result of an idempotent method its a guaranteed bug.
Though this type of lint checking is useful having code with no effect in the program is not really a bug. People using macros or code generators might have to workaround it - maybe by redundant assignment to a variable which would never be used - a bug?
Plus, if the function is not completely side effect free (=pure) people could call it for whatever reason not use its output (dunno, timing issues etc.).
qualidafialSat 23 Jan 2010
pure and idempotent are not the same thing. I keep seeing them referred to in this thread as if they were interchangeable.
imdempotent is when an operation can be executed once or repeatedly and the final result is the same, provided the inputs are the same every time.
example: List.add is not idempotent because calling it repeatedly adds multiple items. List.set(Int, V) is idempotent because calling repeatedly with the same arguments is no different than calling it once: the element at the index is just set to the same object over and over.
brianSat 23 Jan 2010
pure and idempotent are not the same thing
You are right - idempotent is probably not the term we want to use. In fact I'm using that term incorrectly in the List/Map docs.
qualidafialSat 23 Jan 2010
Just thinking out loud: what if we approached this from the opposite side, and marked nonpure methods as @mutator?
brianSat 23 Jan 2010
Just thinking out loud: what if we approached this from the opposite side, and marked nonpure methods as @mutator?
Always good to change directions of thought, but I think forcing you add that facet to all your methods would be pure evil :-)
tompalmerMon 25 Jan 2010
Deleted. Just saw I missed the recent comments on "idempotent".
tompalmerMon 25 Jan 2010
Since the proposed feature requires using the function call as an expression, how about @expr, @exprOnly, @expression, or something like that?
As in, it can't be used as a statement.
brianMon 25 Jan 2010
@exprOnly is actually technically pretty accurate from the compiler's point of view (not sure that would make perfect sense for the normal guy slinging code)
qualidafialTue 26 Jan 2010
Always good to change directions of thought, but I think forcing you add that facet to all your methods would be pure evil :-)
That's what I first thought about requiring the override keyword. :-)
For the sake of argument let's consider two scenarios:
Introduce two keywords pure and mutator, and require that every method declare one or the other. Field getters would be implied pure, and setters mutator, unless a custom getter/setter blocks were explicitly provided, in which case the pure or mutator keyword would be required.
Use static analysis at compilation to determine whether the method is pure or mutator, and store this information in the fcode. Methods could explicitly declare mutator. Native methods would have to be documented one way or the other.
I see a future possibility here for:
lazy evaluation similar to e.g. Haskell,
ability for the compiler to certify pureness of a method, and report violations, and (the original intent)
ability to require that the result of a pure method be used at the call site.
Just some ideas.
brianTue 26 Jan 2010
Part of me likes the idea of pure (or const methods). But I think we need to have some serious benefits in mind before we tackle something like that. Just adding the feature to check return usage certainly isn't justification (which hints more towards to facet solution). And I'm not sure lazy evaluation (Haskell style) is something I'd even want to consider for Fantom (although some small usages of lazy might be nice).
tacticsTue 26 Jan 2010
although some small usages of lazy might be nice
I might as well throw this out there, since it's a lazy feature: generators. They are very nice in Python, and you get do all sorts of streamy stuff with them that's a hassle to do otherwise.
jodastephenTue 26 Jan 2010
If I implement a method, it may initially begin by being pure and not affecting state. But I may later, perhaps just for debugging, add a system.out, or other kind of logging that makes the method have a side effect.
As the API writer, I shouldn't need to change the exposed @mustUseReturn status just to add debug code. A heavily enforced const or pure approach wouldn't allow this.
BTW, this whole thread came from two distinct ideas
@idempotent, or perhaps @nonMutating - due to the number of methods in List that document this as a fact (and which should really be exposed as machine readable info
@mustUseReturn - the desire to avoid bugs caused by not using a return value (very common with immutable objects at the language level)
I think the thread is complex because these two things have become linked. I'm not sure that they need to be. (I can see why a heavily typed language might link them, but heavily typed isn't Fantom)
One final use case I've had before is to be able to dump the content of an object including the result of non-mutating methods as well as fields. These facets should help solve this use case.
jodastephen Thu 7 Jan 2010
Fantom does well at stamping out bugs that occur again and again via language features. This is Great, so I'd like to propose another repetitive bug to remove:
Of course, this code doesn't work as expected, because we forgot to assign the result of
str.upper()
back to the variable. This bug is one we've probably all done.Given Fantom has true immutable classes, this is likely to be a more common bug in Fantom.
The proposal is to add a facet, or other feature, that forces the return value from a method to be used by the caller:
I hope that the bug-squashing power of this is obvious :-)
tactics Thu 7 Jan 2010
The case I always see is
sort
. Every time I learn a new language, I have to go to the docs to look up whether or not sorting is in-place in the language.I would rather see it issue a warning instead of an error, though.
casperbang Thu 7 Jan 2010
That would be a great feature, but I agree with tactics that it should be a warning. The Java compiler has shown us that cranking static checking too high up in the compiler just gets annoying. We all know production code should compile without warnings anyway no?!
helium Thu 7 Jan 2010
What is the more common case? That the return value has to be used or that it's only optional? Depending on the answer to that question maybe this behavior should be the default for all methods that have return types other than
Void
and you'd explicitly mark methods with @OptionalReturn.jodastephen Thu 7 Jan 2010
Warnings are evil, and just ignored, because you get too many of them. You also need an ignore warnings element. Please lets not add warnings to Fantom.
Possibly. But as an error, I think it wants to be the way I suggested, and only applicable for idempotent, safe methods that return a new instance.
brian Fri 8 Jan 2010
Since most APIs are designed to return
This
, the common case is by far that you don't need to use the return value.So this absolutely has to be an opt-in feature rather than an opt-out feature.
I think the basic idea is really good. For example we already trap special cases as not statements. For example if you try to use a field getter as a complete statement today it is a compiler error.
So I am board with supporting the feature and making it an error (versus a warning).
The question is what is the best to think about the problem?
The way I think about the problem is deeper than just an annotation. A method which doesn't produce a side effect is "pure" from a functional perspective. Knowing that a method is pure has all sorts advantages for potential compiler optimizing and "safe scripting". One those advantages is that you must use the return value. There is no point in calling a pure function without using its result (no more so than calling a field accessor).
So what is the best way to annotate a "pure" method? I'm thinking the
const
keyword might be the right solution to jive with the rest of the language.But does that put restrictions on what you do inside the method?
helium Fri 8 Jan 2010
The easy way would be to say that pure function can only call other pure functions. That makes sure that the function is truly pure, but might be to restrictive.
And while it's true that it doesn't make sense to call pure functions without using their results these are not the only functions. You don't call
File.readAllStr
orRange.random
just to ignore the result but those functions are definitely not pure.tactics Fri 8 Jan 2010
I had an idea for a language feature a while ago which was essentially "purity inference." You mark primitive functions as pure or impure, and methods built on top of only pure function inherit their purity. So an
echo
or logging would be impure, but math methods, but string manipulation would all be pure.I never bothered to work out the details, but the idea always seemed kind of neat. Maybe it fits here. It the compiler could infer purity instead of having to explicitly mark it, that'd reduce code clutter. Then, if you want to override the default, you could use @pure (for when you want to echo-debug a pure method or whatever).
helium Fri 8 Jan 2010
Disciple has an effect systems with full inference, but I'm not sure this fits to Fantom.
IIRC D has a simple
pure
annotation. I think that is more like what we might see in Fantom.Some edge cases with inference: All virtual or abstract methods could be overridden in an impure way so they had to be considered impure. Anything that uses the FFI would have to be considered impure. I don't know if this might ever be a problem, but there might be a situation where you think one of your methods should be pure but the compiler infers that it's not and then you have to walk down recursively all the methods that this methods calls to find the root of the problem.
brian Fri 8 Jan 2010
True, but I think these are exactly the same cases where you probably just want a warning, versus a full blown error right? I think of it as language restrictions versus more of a "Fantom Lint".
I was thinking of D's model myself.
I think it just requires subclasses to maintain the contract, so they must be pure too.
Very true, this is just like we have to assume every FFI call is nullable. Although eventually databases which annotate Java/C# methods as pure or non-nullable could eventually be used.
DanielFath Fri 8 Jan 2010
A side note why wouldn't
work? Is it improtant to have that
str=str.upper
?helium Fri 8 Jan 2010
jodastephen thought for whatever reason that
upper
is a method that mutates the string itself instead of returning a new string.It's not important to write
str = str.upper
and your example is perfectly fine.katox Fri 8 Jan 2010
This sounds good on paper but it doesn't really scale. Try to make a
const
method in C++ - there is always something what prevents you to do it. Plus trying to workaround this by@ItIsPureCallBelieveMe
makes things even worse.@pure
could be a nice performace booster too but I don't think it is usable enough to be worth all the hassle.helium Fri 8 Jan 2010
Third party code that is not const-correct. Other than that I never had problems. But I'm not really sure that this is on-topic.
jodastephen Sat 9 Jan 2010
I think that the discussion demonstrates ow easy it is to get sucked into FP like solutions, and that isn't Fantoms area.
My proposal is for something (facet/keyword) that a user adds that forces use of the return type. While I'm OK to see that extended to so-called pure functions, I think terminology becomes important. In other words, Fantom mustn't use FP terminology or wording when describing the feature.
I'd also not thought about the compile enforcing the facet/keyword wrt to the code called internally. That seems overly restrictive in a looser language like Fantom. I see this feature as removing bugs for the method caller, not constraining the method writer.
Oh, and,
should compile, because the upper() method result is being used.
DanielFath Sat 9 Jan 2010
I like joda's reasoning. Making things pure seems like such a hassle. Of course you could wrap all unpure code in
pure
wrappers.andy Sun 10 Jan 2010
Interesting discussion, and certainly valuable. But on the surface, this seems like a feature thats better implemented in an IDE.
Marking methods as side-effect free is interesting, though I'm not sure how valuable/applicable that would be in a language like Fantom. I think we have the correct focus now - immutable data structures and no shared state b/w threads.
tcolar Tue 12 Jan 2010
I think this discussion and others are turning a little bit into :Function programming or not".
Personally I really like Fantom approach of picking best features of both world without being a "my way or the highway" language like a lot of the other newer language seem to be.
A programming language is a tool, I want it to be flexible and easy to use.
On the particular matter I think it's a bit over the top, could be a facet I guess.
brian Thu 21 Jan 2010
I think we should continue this discussion, because it is a valuable idea - especially in conjunction with changes to
List.sort
to return a new value.I think this is a fairly easy feature to implement and provides a lot of bang for the buck.
But the key issue for me is naming the facet. Ideas:
const
keywordNone of those really work for me - ideally I'd like a simple, single word that isn't as limited as "pure" and not as obscure as "idempotent".
DanielFath Fri 22 Jan 2010
I'm curious,
const
seems ideal. It isn't limited as @pure and and isn't as obscure as @idempotent.katox Fri 22 Jan 2010
I got lost what is the current proposal - the discussion diverged a lot from the first post.
If it is about functions with no side effects then
const
keyword seems to be fine. This would however need someone to go through all core API and mark everything accordingly.One of the things I don't like on pure/const approach is impossiblity of creating transparent caching classes (later on) - you'd have to refactor all classes that use the code (not only inherit from).
The other thing are wrappers which could "correct" unpure method and functions to pure. I don't like that at all. It feels like casting immutable to mutable and forcing changes anyway. In the end it guarantees you nothing (but could induce hideous issues).
Having pure things is very natural when immutability is the default - which is not the case of Fantom.
brian Fri 22 Jan 2010
I don't want to turn this feature into a full blown pure/const function type system.
I'd prefer to focus on the more manageable task of what Stephen originally proposed: indicating that a method doesn't do anything, and that if you don't use the result then you shouldn't bother calling the method.
However I don't think the right semantics if mustUseReturn, I think it is more valuable to nail down something a little more specific about the method that might be generally useful - such as
@idempotent
.DanielFath Fri 22 Jan 2010
@non-volatile
(or@volatile
if it is the less common case ) perhaps?andy Fri 22 Jan 2010
I don't see how those are different. The only value in this feature is making the compiler verify you have not modified any state in your method. That screams keyword to me personally, and while I don't like overloading the
const
term, it seems most appropriate here.brian Fri 22 Jan 2010
That isn't the only value - that is actually a very different problem than the one Stephen was proposing. What Stephen was proposing is to solve was bugs in the caller of the method - that a caller to method like
List.sort
must use the return value or else it is a bug.The flip side is checking for the person actually writing the method that if they declare it as "pure" then the compiler verifies nothing other than pure calls are used. But that is an entirely different problem and a huge feature because it gets into all sorts of thorny issues like ability to use reflection, dynamic invoke, etc.
So I think the simple low hanging fruit here is just marking a method which provides error checking for the method caller, not the method implementator.
andy Fri 22 Jan 2010
I realize that, and that is why I have a pretty big objection to this. Because the root issue is
const
methods. In which case, you get this "must use return value" for free.The
List.sort
is a documentation issue. If you don't document how a method behaves, you're probably not going to add the facet either, so we end up with a half-ass system here. I think this oversteps the compilers duties given how it will be used/implemented.I feel pretty strongly we solve
const
methods, or do nothing.jodastephen Fri 22 Jan 2010
I think we all agree that a method like
List.sort
should document how it behaves in order to be usable. The question is whether this is just words, or more than that.I would place this in a similar boat to null specification. In Java, most APIs don't specify whether each parameter does or does not accept null, and the same with the return type. However, for my $work, we have a convention whereby each method specifically states the null behaviour in Javadoc. (These days, this could be an annotation). For Fantom, we eliminated this silliness, by having null in the type system.
The same applies here. No matter what, the information should be documented. But why just document it in text, when it could be machine readable? (That the machine readable approach allows the "no expression" behaviour of the original request is almost a side benefit).
And Andy, you are right to say that users might not document the methods with the annotation/keyword. But thats equally true of any documentation, as
List.sort
shows. If you can think of this as just formalised documentation, with a nice side benefit, perhaps I might persuade you as to its usefulness.BTW, I agree that a full solution is locked down const methods, with detailed checking by the compiler as to the truth of the pureness of the method. Such an approach would be entirely appropriate in a language like Scala which uses the compiler as a hammer to attack every problem.
But, Fantom isn't Scala. Its the middle ground language between extreme typing (Scala) and little typing (Ruby). It uses just enough type information to catch the main problems that occur without actually getting in the way excessively.
In summary: Full pureness checking is entirely at odds with Fantom-style. Avoiding common programmer errors and moving documentation to machine readable is entirely in line with Fantom-style.
brian Fri 22 Jan 2010
I am in agreement. I don't think full pure function checking makes sense in Fantom. But I think if we go to the trouble of documenting that a method is idempotent (which I do in List and Map), then might as well use a facet so that it is machine readable and enables extra error checking (be it in the core compiler or in a compiler add-on).
andy Fri 22 Jan 2010
I completely agree; re my original response. My objection was enforcing this constraint without actually verifying that the code is indeed idempotent seems fragile.
So my stance is:
@indepotment
facetjodastephen Fri 22 Jan 2010
OK, so we agree on points 1, 2 and effectively 4 too. The only question is whether "handy for tool support" should include the compiler, as per the original request of the thread.
I struggle to understand why Fantom shouldn't, at the compiler level, use the information at the call-site to eliminate a whole class of bugs. That seems eminently sensible and developer friendly.
BTW, I agree that the IDEs cuold do it, but why should they need to do the work when it can be a core feature?
Anyway, an
@idempotent
facet or similar keyword would be a great first step.casperbang Fri 22 Jan 2010
Sort of like checked exceptions became a core feature? Hmm.
jodastephen Fri 22 Jan 2010
Huh? I don't think there is any connection with checked exceptions.
If you don't use the result of an idempotent method its a guaranteed bug. Exception catching is way more complex and subtle than that.
andy Fri 22 Jan 2010
Because by making the compiler enforce it, it is no longer a documentation feature, it is a language feature, and there is no way to guarantee it actually works as advertised. It will be taken for granted, and is ripe for a new class of bugs, where expected behavior is not guaranteed.
So, lets remove the "idempotent-ancy" from the equation, and go back to just
@mustUseReturn
. We are saying the compiler is forcing me to use result of this method. I'm on the fence with that, but feel like without the side-effect free guarentee, this seems of little value. But I wouldn't hold it up if everyone else likes it.katox Fri 22 Jan 2010
Though this type of lint checking is useful having code with no effect in the program is not really a bug. People using macros or code generators might have to workaround it - maybe by redundant assignment to a variable which would never be used - a bug?
Plus, if the function is not completely side effect free (=pure) people could call it for whatever reason not use its output (dunno, timing issues etc.).
qualidafial Sat 23 Jan 2010
pure and idempotent are not the same thing. I keep seeing them referred to in this thread as if they were interchangeable.
imdempotent is when an operation can be executed once or repeatedly and the final result is the same, provided the inputs are the same every time.
example: List.add is not idempotent because calling it repeatedly adds multiple items. List.set(Int, V) is idempotent because calling repeatedly with the same arguments is no different than calling it once: the element at the index is just set to the same object over and over.
brian Sat 23 Jan 2010
You are right - idempotent is probably not the term we want to use. In fact I'm using that term incorrectly in the List/Map docs.
qualidafial Sat 23 Jan 2010
Just thinking out loud: what if we approached this from the opposite side, and marked nonpure methods as @mutator?
brian Sat 23 Jan 2010
Always good to change directions of thought, but I think forcing you add that facet to all your methods would be pure evil :-)
tompalmer Mon 25 Jan 2010
Deleted. Just saw I missed the recent comments on "idempotent".
tompalmer Mon 25 Jan 2010
Since the proposed feature requires using the function call as an expression, how about
@expr
,@exprOnly
,@expression
, or something like that?As in, it can't be used as a statement.
brian Mon 25 Jan 2010
@exprOnly
is actually technically pretty accurate from the compiler's point of view (not sure that would make perfect sense for the normal guy slinging code)qualidafial Tue 26 Jan 2010
That's what I first thought about requiring the
override
keyword. :-)For the sake of argument let's consider two scenarios:
pure
andmutator
, and require that every method declare one or the other. Field getters would be impliedpure
, and settersmutator
, unless a custom getter/setter blocks were explicitly provided, in which case thepure
ormutator
keyword would be required.pure
ormutator
, and store this information in the fcode. Methods could explicitly declaremutator
. Native methods would have to be documented one way or the other.I see a future possibility here for:
Just some ideas.
brian Tue 26 Jan 2010
Part of me likes the idea of
pure
(orconst
methods). But I think we need to have some serious benefits in mind before we tackle something like that. Just adding the feature to check return usage certainly isn't justification (which hints more towards to facet solution). And I'm not sure lazy evaluation (Haskell style) is something I'd even want to consider for Fantom (although some small usages of lazy might be nice).tactics Tue 26 Jan 2010
I might as well throw this out there, since it's a lazy feature: generators. They are very nice in Python, and you get do all sorts of streamy stuff with them that's a hassle to do otherwise.
jodastephen Tue 26 Jan 2010
If I implement a method, it may initially begin by being pure and not affecting state. But I may later, perhaps just for debugging, add a system.out, or other kind of logging that makes the method have a side effect.
As the API writer, I shouldn't need to change the exposed
@mustUseReturn
status just to add debug code. A heavily enforcedconst
orpure
approach wouldn't allow this.BTW, this whole thread came from two distinct ideas
@idempotent
, or perhaps@nonMutating
- due to the number of methods inList
that document this as a fact (and which should really be exposed as machine readable info@mustUseReturn
- the desire to avoid bugs caused by not using a return value (very common with immutable objects at the language level)I think the thread is complex because these two things have become linked. I'm not sure that they need to be. (I can see why a heavily typed language might link them, but heavily typed isn't Fantom)
One final use case I've had before is to be able to dump the content of an object including the result of non-mutating methods as well as fields. These facets should help solve this use case.