huh, #Python does not distinguish `return None` from having no return at all

this is a bit weird coming from #Lua

(then again, Lua's way is also a bit weird)

in Lua:
`(function()end)() == nil` is true but
`(function()end)()` and `(function() return nil end)()` print different things at the REPL

@grainloom huh, in ruby a void function just returns nil:

def nnn
nil
end
# => :nnn
def voidvoid
#noop
end
# => :voidvoid

nnn # => nil
voidvoid # => nil
nnn == voidvoid # => true

@bonzoesc yuh, in Lua there is actually a stack for the return values or something
in Python, you can return multiple things but it's actually just syntax sugar for returning a tuple, whereas in Lua you are pushing things onto a stack

eg. you can do
local a, nil1, nil2 = (function()return 1 end)()
-- a == 1 and nil1 == nil and nil2 == nil

@bonzoesc and you can also ignore a few returned values, this is often used instead of result types:
loadstring("bad source code")
will return nil and an error message
loadstring("return 1")
will return a funtion

if you don't expect an error then you get nil and trying to use that will sooner or later blow up

@bonzoesc
the proper way to handle that would be
local f = assert(loadstring(source_code))

if given a false-y value, assert will call error with its second parameter

@grainloom yeah, ruby does the destructuring assignment like that (but with Arrays since no tuples)

not sure how i feel about it all, kinda neat that all these languages found different things to work for different people

@grainloom In Lua 5 and LuaJit 2 both seem to print nothing at the REPL.
@clacke
LuaJIT 2.0.5 -- Copyright (C) 2005-2017 Mike Pall. http://luajit.org/
JIT: ON CMOV SSE2 SSE3 SSE4.1 fold cse dce fwd dse narrow loop abc sink fuse
> =(function()end)()
> =(function() return nil end)()
nil
>
@clacke in versions before 5.3 you had to prefix lines with `=` if you wanted it to treat it as an expression to print
@grainloom I love Python buy the way it does return from methods is very non intuitive
@feoh @grainloom If I'm outside Python for just one day, I immediately forget that the return needs to be explicit, and you can't just end your function with `blah` to return blah.

And then in lambdas that's exactly how you do it, then the return is implicit again. πŸ˜€

@clacke @feoh you usually don't have to care about it in Lua either, even if you table.pack the results, you could still table.unpack it for another function call and its params would still be nil

printing an explicit nil return vs not printing anything in the absense of a return is probably one of the only times this comes up (but it's pretty nice to have there, because you wouldn't want functions you only run for side-effects to pollute the REPL)

@grainloom @clacke I've been hacking some lua of late as a part of #pico8 and I've REALLY been enjoying the heck out of it! Such a pleasantly concise, straight forward little language!

@grainloom Can you explain? I ask because one of the things that threw me coming from #ruby to #python is that in Ruby you get implicit returns:

```
def return_moo()
"moo"
end
```

isn't just valid but idiomatic Ruby!

Whereas in Python doing that results in no value returned at all. Confused the CRAP out of me for the longest time!

Making a rediscovery drive? πŸ™‚

Python requires a return because point 2 in the Zen of Python is "explicit is better than implicit".

That said, as I noted in libranet.de/display/0b6b25a8-7… it confuses me too when coming back from non-Python.

In fact, I noted just the other month that I still have this issue even after a full year of full-time Python, as soon as I step out and step back. 😁
Claes Wallin πŸ‡ΈπŸ‡ͺπŸ‡­πŸ‡°

@Feoh @Rain 🚱 If I'm outside Python for just one day, I immediately forget that the return needs to be explicit, and you can't just end your function with...

@feoh yeah, Ruby copied the "every statement is an expression" thing from Scheme.
I haven't used Ruby much, so I'm not sure how its return mechanism works, but in Lua, an expression can result in an arbitary number of values, kinda like in Forth, but Python does some weird tuple packing/unpacking thing.
Usually you don't have to worry about the difference, just know that you can do
status = pcall(f)
or
status, result = pcall(f)
where only the first returned value is bound, or table.pack(pcall(f)), where everything is packed into a table.
@feoh really this distinction only becomes important when you want to wrap arbitrary functions.
you can run into stuff like table.pack(nil).n being 1, but #(table.pack(nil)) being 0, but it's not really important, because when you unpack it again and assign the results, nil will still be nil.
like:
x=table.unpack(table.pack())
x=table.unpack(table.pack(nil))
both result in x being nil
@grainloom @feoh Python doesn't do any "sugar" or "special tuple thing" around return values, every function just returns one value and that's it.

If you don't explicitly return anything, the value is None. Comma-separated values are a tuple and that's the tuple syntax and that's it. If you return a tuple, that tuple is the return value.

People get tricked into believing that parentheses are part of the tuple syntax, but the comma is what does it. The parentheses are just often required not to interpret the comma as the argument separator, but the return statement is not a function call and has no argument separator, so a comma creates a tuple.
In [1]: a = 23, In [2]: a Out[2]: (23,) In [3]: a, b = 23, 42 In [4]: a Out[4]: 23 In [5]: b Out[5]: 42 In [6]: def f(): ...: return "foo", "bar" ...: In [7]: a, b = f() In [8]: a Out[8]: 'foo' In [9]: b Out[9]: 'bar'
A tuple on the left of an assignment is a destructuring assignment and takes a sequence on the right. A tuple is one possible type of sequence.
In [18]: zip((1, 2), ("a", "b")) Out[18]: <zip at 0x74fa28e0c0> In [19]: a, b = zip((1, 2), ("a", "b")) In [20]: a Out[20]: (1, 'a') In [21]: b Out[21]: (2, 'b') In [22]: (x + 1 for x in (0, 1)) Out[22]: <generator object <genexpr> at 0x74fa3ae200> In [23]: a, b = (x + 1 for x in (0, 1)) In [24]: a Out[24]: 1 In [25]: b Out[25]: 2
@clacke `first, *rest = func_that_returns_list()` is a good one that I use a lot!
@clacke I forget whether or not it works with a generator. I think it does?
@pizza_pal It's just too bad that *rest eats the rest and makes a list instead of just getting the iterator. That's a missed opportunity.
But then again that list semantic is what makes this possible and consistent:
In [30]: a, *b, c = (x for x in (1, 2, 3)) In [31]: a Out[31]: 1 In [32]: b Out[32]: [2] In [33]: c Out[33]: 3
@clacke that would be cool. i think you can do stuff like that in clojure.
Oh Clojure, is there anything you won't do? πŸ™‚

Still looking for an excuse to use transducers in Scheme, now that they've been ported.
Spent last night reading a bit about Clojure destructuring assignment when I should have been doing other things. πŸ™‚
@grainloom
Well, something like this. Actually, in #Python *every* function returns None by default - thus is: if no return statement is reached/executed.
You can imagine a "return None" at the very end of every function.