evhub / coconut

Simple, elegant, Pythonic functional programming.
http://coconut-lang.org
Apache License 2.0
4.09k stars 125 forks source link

Extend assignment function notation / make more things expressions #190

Open Nexus6 opened 8 years ago

Nexus6 commented 8 years ago

I think it would be useful if "match" and "case" statements were actually expressions. For example, I'd like to be able to do things like:

def whats_this(foo) =
    case foo:
        match 0:
            #<do something>
            "Zero"
        match 1:
            #<do something else>
            "One"
    else:
        "Dunno"

assert "Zero"   == whats_this(0)
assert "One"    == whats_this(1)
assert "Dunno"  == whats_this(2)
Nexus6 commented 8 years ago

Also, as a precondition for this syntax, assignment functions would need to be changed to return the last expression in the chain of execution rather than the expression on the last line of the function. For example:

def new_assign(foo) = 
    if foo == 0:
        "Zero"
    elif foo == 1:
        "One"
    else:
        "Dunno"

assert "Zero"   == whats_this(0)
assert "One"    == whats_this(1)
assert "Dunno"  == whats_this(2)

I think this is desirable on its own as it simplifies the use assignment functions. There'd be no need to create a temporary variable to hold a return value that's subsequently placed on the last line of the function, and no need for creating explicit early "return" statements either.

evhub commented 8 years ago

@Nexus6 Yeah, I totally agree that this would be useful--it's something I've been wanting to implement since I resolved #159. Putting this issue on the v1.2.1 milestone.

evhub commented 7 years ago

As per Gitter discussion, implementation of this should involve having --strict warn about the use of normal return statements.

evhub commented 3 years ago

I think the danger of this is that it makes assignment functions much less clear in terms of what they're returning—if you want to return something from a nested block, I think it's a good idea to just force an explicit return statement there.

akdor1154 commented 2 years ago

This is really a subcase of everything-is-an-expression, isn't it? That's why it works in the languages people are coming from (ML-based, Rust, Haskell, ...) If you would consider that approach for v2 it would be very consistent from a user perspective.. but would almost certainly mean throwing away py3-is-valid-coconut.

evhub commented 2 years ago

@akdor1154 Allowing almost everything as an expression actually would likely be possible without breaking the rule that all valid Python 3 is valid Coconut, but I'm not convinced that there's actually enough of a use case for it to justify implementing it. In particular, I think statement lambdas (especially pattern-matching statement lambdas) already cover most of the use cases for something like that.

akdor1154 commented 2 years ago

Its not so much about filling use case checkboxes, it's more (again, just from user perspective here) about consistency.

There are already special cases that work towards this: your alternative ternary operator (not needed if if is an expression), statement lambdas (would be sometimes not needed if def fn is an expression), probably more..

With everything-is-expression, the above special cases mostly become unnecessary, and the requests for other special cases (why not a match expr to go with ternary expr? Why does a statement lambda on my last line return a func, but a def func return None?) go away.

As before, the languages Coconut is inspired by largely work this way already - as a user I find the python 'actually this thing is a statement and will silently eval to None' paradigm to be jarring in Coconut's otherwise functional environments.

In practice with these other languages I haven't noticed an issue with clarity around what a deeply nested expression returns - as long as there is clear syntax to delineate the expression as a unit, which Py/Coconut's whitespace does beautifully.

koraa commented 1 year ago

Honestly, there are a bunch of patterns where this would be useful.

Getting rid of multiple return statements reduces visual clutter and lets me focus on what I actually want to read. This is important for accessibility – personally I have to use giant fonts for vision reasons.

Having more expressions available also lends itself to a data flow oriented programming style, which is why I would like to use coconut in the first place. The way expressions vs statements are handled in coconut right now feels a bit out of place for a language that aims to facilitate a functional programming style.

I agree, that the syntax you get from allowing statements as expressions without any extra syntax elements, a bit awkward:

x = if foo == 0:
    "Zero"
elif foo == 1:
    "One"
else:
    "Dunno"

Maybe enforcing a syntax with round braces would be better:

x = (
  if foo == 0:
      "Zero"
  elif foo == 1:
      "One"
  else:
      "Dunno"
)

Or with a special keyword:

x = do (
  if foo == 0:
      "Zero"
  elif foo == 1:
      "One"
  else:
      "Dunno"
)
ilka-schulz commented 1 year ago

I love how if and match are expressions, not statements in Rust :crab: and this is one of the most painful things when programming in Python (along lambdas not being closures which is solved beautifully in Coconut). I would love if this feature could be added to coconut! :coconut:

evhub commented 1 year ago

I'm not currently sold on this, but I certainly see the appeal. Here are what I see as the major hurdles to something like this:

  1. Many of the proposed syntaxes would create confusion with the Python constructs—which Coconut has to keep compliance with, since one of the major guarantees of Coconut is that all valid Python (on the latest Python version) should work the exact same way in Coconut. If sometimes your if statements result in the thing inside them being implicitly returned, but other times they don't, depending on whether you used it as a statement or an expression, I think that could get very confusing.
  2. This would be the only instance where line breaks and indentation matter inside of expressions. In Python, and by extension in Coconut, line breaks and indentation inside of parentheses/brackets/braces are ignored (which is the only way to get a line break in an expression). All of the proposed syntaxes for this above would break that abstraction, which I'm not super keen on breaking: Python's/Coconut's whitespace rules are currently extremely clear, and this would make them a lot more complicated.

I'm open to suggestions if anyone has ideas for this that would rectify the two issues above.