Code style: Using single-expression functions without returning meaningful values

kotlin · · 91 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>I keep seeing one-line functions that don&#39;t return meaningful values being written as single-expression functions:</p> <pre><code>fun output() = println(&#34;The total is $total.&#34;) </code></pre> <p>I&#39;ve seen this in many projects, but it doesn&#39;t seem right to me. I realise that it compiles and runs, because technically the function returns Unit, but in my view this is an abuse of the syntactic flexibility of Kotlin.</p> <p>Code should say what it means. To me, the above code says, &#34;This function prints text and then returns a meaningful value.&#34; Perhaps this value is a success value, or the number of characters actually printed. But of course this is not true.</p> <p>I think the following better expresses the real meaning:</p> <pre><code>fun output() { println(&#34;The total is $total.&#34;) } </code></pre> <p>This reads as, &#34;This function prints text.&#34; There&#39;s no misleading idea of actually returning anything (again, even if Unit is technically returned). I realise that this entails longer code, but personally I would pick simplicity over brevity.</p> <p>What do you all think? Do you agree or disagree? I&#39;ve never seen this mentioned in style guides, and I can&#39;t find a single discussion of this online.</p> <p>I would make one final argument: If you want to use the single-expression style for every one-liner, you won&#39;t be able. Here is an example:</p> <pre><code>fun update() = total += lastCharge // THIS WON&#39;T COMPILE </code></pre> <p>So, in cases such as this, you would have no choice but to drop the style, which looks inconsistent across a code base.</p> <hr/>**评论:**<br/><br/>redditsoaddicting: <pre><p>Technically...</p> <pre><code>fun update() = {total += lastCharge}() </code></pre></pre>shadowdude777: <pre><p>oh god no</p></pre>fGeorjje: <pre><p>Beat me to it!</p> <p>I&#39;ve actually done this...once...definitely going to hell</p></pre>eliteSchaf: <pre><p>Kotlin style guide says that single line expressions should only be used for expressions that actually return something</p></pre>RaisedByTheInternet: <pre><p>Do you have a link? I don&#39;t see this clearly stated in the official Kotlin style guide.</p></pre>eliteSchaf: <pre><p>It could be stated more prominent in the style guide. For now we have</p> <blockquote> <p>Prefer using an expression body for functions with the body consisting of a single expression.</p> </blockquote> <p><del>An Expression is some code that returns something.</del></p> <p><del>The <code>println(&#34;Hello World&#34;)</code> doesn&#39;t return anything and thus is a Statement.</del></p> <p><del><a href="https://blog.kotlin-academy.com/kotlin-programmer-dictionary-statement-vs-expression-e6743ba1aaa0" rel="nofollow">https://blog.kotlin-academy.com/kotlin-programmer-dictionary-statement-vs-expression-e6743ba1aaa0</a></del></p></pre>RaisedByTheInternet: <pre><p>I think you&#39;re right, but the issue I have is that <code>println(&#34;Hello&#34;)</code> is actually an expression in the Kotlin language. The link you posted also confirms this:</p> <blockquote> <p>Note that in Kotlin every function returns at least Unit, therefore every function invocation is an expression.</p> </blockquote> <p>For this reason, anybody arguing against me could say that the style guide, as it&#39;s written right now, is fully supporting <code>fun output() = println(&#34;Hello&#34;)</code>.</p> <p>It seems to me that the only way to solve this problem is for the guide to say something like, &#34;Do not use single-expression functions when the return value is Unit.&#34;</p> <p>Edit: Though even that isn&#39;t perfect, because there are cases where people intentionally return Unit as a value. An example is a generic function whose generic return value (even if it ends up being Unit) is potentially stored. Maybe it needs to be something like, &#34;Do not use single-expression functions when the return value is not meaningful.&#34;</p></pre>eliteSchaf: <pre><blockquote> <p>Note that in Kotlin every function returns at least Unit, therefore every function invocation is an expression.</p> </blockquote> <p>Oh, I actually think the author is wrong here. (Just skimmed throught the blog post before I posted it here xD</p> <p>IMO an expression produces a meaningful value, whereas a statement (even if it returns Unit) only does side-effects and doesn&#39;t return a meaningful value.</p> <p><a href="https://fsharpforfunandprofit.com/posts/expressions-vs-statements/" rel="nofollow">https://fsharpforfunandprofit.com/posts/expressions-vs-statements/</a></p></pre>Cilph: <pre><p>Your opinion on what is a <em>useful</em> expression aside, Kotlin does consider functions returning Unit to be expressions. I believe only assignments (and plusAssign therefore too) are statements.</p></pre>eliteSchaf: <pre><blockquote> <p>Your opinion on what is a useful expression aside</p> </blockquote> <p>Where did I say something about whether an expression is useful?</p> <blockquote> <p>Kotlin does consider functions returning Unit to be expressions</p> </blockquote> <p>I&#39;m not entirely sure about that</p></pre>Cilph: <pre><p>You state an expression produces a meaningful value, while the rule is an expression produces <em>any</em> value. Unit is a valid type in Kotlin, much like Void in Java.</p></pre>eliteSchaf: <pre><p>Yes, but I&#39;ve never said that <code>println(&#34;Hello World&#34;)</code> isn&#39;t useful, just that from a semantic point of view it feels more like a statement, because it doesn&#39;t produce a value that you work with.</p></pre>sheatrevor: <pre><p>You don&#39;t have to be sure about it! It&#39;s easy to test. To test, try compiling/running the following code:</p> <pre><code>var total: Int = 0 val assignment = (total = 1) </code></pre> <p>When you test that, the Kotlin compiler provides an explanation as to why it won&#39;t allow it.</p> <pre><code>error: assignments are not expressions, and only expressions are allowed in this context val assignment = (total = 1) ^ </code></pre> <p>The position of the <code>^</code> in the error message provides the clarification that Kotlin requires an <em>expression</em> on the right-hand-side of a variable assignment:</p> <pre><code>val assignment = (expression) </code></pre> <p>Similarly, if we write the following function...</p> <pre><code>fun attemptToReturn() { var total: Int = 0 return total = 1 } </code></pre> <p>...the compiler fails for exactly the same reason.</p> <pre><code>error: assignments are not expressions, and only expressions are allowed in this context return total = 1 ^ </code></pre> <p>Again, the position of the <code>^</code> in the error message provides the clarification that Kotlin requires an <em>expression</em> on the right-hand-side of a <code>return</code> if there is anything there at all:</p> <pre><code>return (expression) </code></pre> <p>So now, given that these are the rules the compiler is enforcing, we can conclusively determine if a function that returns <code>Unit</code> is considered a <em>statement</em> or if it is considered <em>expression</em>. </p> <pre><code>val unit = println(&#34;Hello World.&#34;) </code></pre> <p>This runs just fine; the variable <code>unit</code> is assigned to the value <code>Unit</code>. To verify, we can write...</p> <pre><code>if (unit == Unit) println(&#34;Yes.&#34;) </code></pre> <p>...and this prints...</p> <pre><code>Yes. </code></pre> <p>Similarly, if we write the following function...</p> <pre><code>fun attemptToReturn() { return println(&#34;Hello World!&#34;) } </code></pre> <p>...this also will compile and run just fine. Armed with the knowledge that the compiler requires that the right-hand-side of a <code>return</code> is an <em>expression</em>, we can infer that the <code>Unit</code> return value is a <em>real</em> value, and therefore anything which returns <code>Unit</code> <em>is an expression.</em></p> <p>(Edit: whitespace, formatting, code blocks.)</p></pre>eliteSchaf: <pre><p>From the compiler perspective you are absolutely right. Everything that returns something <em>is</em> an expression.</p> <p>From a semantic point of view</p> <p><code>fun log() = println(&#34;Hello World&#34;)</code></p> <p>just feels like</p> <p><code>void log() { System.out.println(&#34;Hello World&#34;)</code></p> <p>because neither return a value that you work with</p></pre>sheatrevor: <pre><p>The expression does return something. It’s type is called <code>Unit</code>. It’s an object reference by the name of <code>Unit</code>. Further, there is no such thing as a statement in Kotlin. Everything is an expression.</p></pre>eliteSchaf: <pre><blockquote> <p>Further, there is no such thing as a statement in Kotlin. Everything is an expression.</p> </blockquote> <p>What about <code>total += x</code>?</p> <p>And yes, there are statements in Kotlin, even the Style guide mentions that for <code>if</code>/<code>when</code>...</p> <p><a href="https://kotlinlang.org/docs/reference/coding-conventions.html#using-conditional-statements" rel="nofollow">https://kotlinlang.org/docs/reference/coding-conventions.html#using-conditional-statements</a></p> <p>I should&#39;ve worded it a bit different:</p> <blockquote> <p>the purpose of an expression is to create a value (with some possible side-effects), while the sole purpose of a statement is to have side-effects.</p> </blockquote> <p><a href="https://fsharpforfunandprofit.com/posts/expressions-vs-statements/" rel="nofollow">https://fsharpforfunandprofit.com/posts/expressions-vs-statements/</a></p> <p><code>println(&#34;Hello World&#34;)</code> only does side-effects without returning a meaningful value.</p></pre>sheatrevor: <pre><p>I stand corrected! Kotlin <em>does</em> in fact differentiate between statements and expressions. Specifically, it appears that <code>if</code> and <code>when</code> have both statement and expression forms and that variable-assignment is always considered a statement. <em>Nothing else in the programming language is a statement, including methods that return <code>Unit</code>. Methods that return <code>Unit</code> are expressions.</em> Thank you for helping me refine my understanding of the language.</p></pre>eliteSchaf: <pre><blockquote> <p>Methods that return Unit are expressions</p> </blockquote> <p>And here starts the confusion. From a semantic point of view</p> <p><code>fun add1(x: Int) = x + 1</code></p> <p>is very different from</p> <p><code>fun log(msg: String) = println(msg)</code></p> <p><code>log</code> feels more like a <code>void</code>-Method from Java, because usually you ignore the return value</p></pre>sheatrevor: <pre><p>My confusion was actually that I assumed assignment operations <em>also</em> returned <code>Unit</code>. If the language had been designed that way, assignment would <em>technically</em> be an <em>expression</em>, just one which resolved to <code>Unit</code>.</p> <p>Edit: To be clear, I don&#39;t think the <code>log</code> function in your example is <em>good style</em>. I don&#39;t like it. This discussion is not about readability. This discussion is about what the compiler is classifying the code as.</p></pre>hpernpeintner: <pre><p>&#34;Feel right&#34; is highly dependent on your personal experience, but Kotlin showed us (Java developers) many times, that even though things might feel right, they&#39;re probably not. And the other way around: Just because they feel wrong (becuause we know it differently), doesn&#39;t mean they&#39;re. There is a large amount of programmers that totally explode if you tell them that there are languages that return the last statements result implicitely, telling you how wrong this is.... I don&#39;t want to open this discussion, as you refer to single expression functions only here... but I don&#39;t get the point why the return value of the print function makes any point for you here. Unit is not only &#34;technical&#34; - it means here&#39;s returned really just &#34;something&#34;. The one function&#39;s &#34;something&#34; is just as good as the other function&#39;s &#34;something&#34;, so returning one or the other does not make any technical or semantical difference.</p> <p>I tend to write too much text, so I try to be concise: * As soon as you start to read everything as expression and got used to Unit, you won&#39;t have any problem with the first code example anymore. * Maybe our view differs, but the second code example doesn&#39;t express any other meaning than your first one to me. Have to say that I&#39;m writing Kotlin for quite a time now. * I would just use parantheses here and keep the single line. Such a thing would even be possible: fun bla() = (1+1). Then there&#39;s no inconsistency in style throughout the project, although I really find it overrated to keep consistency on such a level.</p></pre>RaisedByTheInternet: <pre><blockquote> <p>The one function&#39;s &#34;something&#34; is just as good as the other function&#39;s &#34;something&#34;, so returning one or the other does not make any technical or semantical difference.</p> </blockquote> <p>I disagree. There&#39;s a big semantic difference between <code>fun print() = println(&#34;Hi&#34;)</code> and <code>fun add() = a + b</code>. The first one returns nothing of interest, while the second one returns a value that is intended to be used.</p></pre>hpernpeintner: <pre><p>You missed my point, because you exluded one sentence of mine before the cited one: Unit is not only &#34;technical&#34; - it means here&#39;s returned really just &#34;something&#34;.</p> <p>Instead of comparing two functions that return different results and asking which one has a more relevant return value (I which is pointless), you should ask whether <code>fun print() = println(&#34;Hi&#34;)</code> and <code>fun print(): Unit { println(&#34;Hi&#34;) }</code> makes a difference. As said, Unit is really just one value, it exists only once. That means it makes no technical and <em>semantical</em> difference from where it is returned, it solely exists to just return &#34;something&#34;.</p> <p>You said several times, that the return of print is nothing of interest. That&#39;s right, but it&#39;s the same uninteresting thing that every function returns if not stated otherwise, so there&#39;s nothing wrong with returning it - explicitely, chained, implicitely, doesn&#39;t really matter.</p></pre>fadefade: <pre><p>Why do you feel that the first example has a bigger expectation of return a value than the second? I&#39;d say they are pretty much equivalent. </p></pre>RaisedByTheInternet: <pre><p>I get this impression from reading the official documentation:</p> <p><a href="https://kotlinlang.org/docs/reference/functions.html#single-expression-functions" rel="nofollow">https://kotlinlang.org/docs/reference/functions.html#single-expression-functions</a></p></pre>BoDlulu: <pre><blockquote> <p>When a function returns a single expression, (...)</p> </blockquote> <p>There are functions that return multiple expressions?</p></pre>sassrobi: <pre><p>I aggree... sort of. For me (too), the single-expression format “fits” when it actually contains an expression. If it’s a single function call, then call that function directly. If the intent was to make some default parameters, or hide the actual implementation etc, then use a proper “abstracting solution”.</p> <p>But... if we approach this as a code readibility problem, well, “function output equals println with some value” is totally readable and understandable for me, so I can live with it :)</p></pre>RaisedByTheInternet: <pre><blockquote> <p>But... if we approach this as a code readibility problem, well, “function output equals println with some value” is totally readable and understandable for me, so I can live with it :)</p> </blockquote> <p>Readability can also suffer. Consider the following:</p> <pre><code>fun updateDatabase() = writeToDisk() </code></pre> <p>What is being returned here? It might be Unit, or it might be Boolean. Your guess is as good as mine.</p></pre>endreman0: <pre><pre><code>fun updateDatabase(): Boolean = writeToDisk() </code></pre></pre>RaisedByTheInternet: <pre><p>What if it returns Unit?</p></pre>endreman0: <pre><pre><code>fun updateDatabase(): Unit = writeToDisk() </code></pre></pre>bbqburner: <pre><p>Well nothing stops you from writing <code>fun something(){ doSomething }</code></p> <p>To be honest, I rather prefer they let us write <code>fun foo() = methodThatReturnsUnit(&#34;bar&#34;)</code> since it&#39;s just a sugar.</p> <p>Consider the below that have no real style difference:</p> <ul> <li><code>fun add() : Int = 1 + 1</code></li> <li><code>fun foo() : Unit = doSomethingThatReturnUnit</code> (compile error tho)</li> </ul> <p>With single line expression, it just let you skip writing the return type. Funny enough, <code>fun foo() = Unit</code> is actually a valid expression. </p></pre>eliteSchaf: <pre><p>Is it really worth the possible confusion to save like two keystrokes?</p></pre>Exallium: <pre><p><code>fun update(): Unit = ...</code> not work?</p></pre>BoDlulu: <pre><p>I really don&#39;t think there&#39;s a real problem here. Single line functions are cool, but if you think one is confusing, refactor it to use the longer syntax.</p> <p>In my own experience, I don&#39;t find single line functions returning Unit particularly confusing. YMMV</p></pre>
91 次点击  
加入收藏 微博
0 回复
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet