tl icon indicating copy to clipboard operation
tl copied to clipboard

Required / Optional function arguments?

Open pdesaulniers opened this issue 4 years ago • 10 comments

It would be nice if TL could prevent this kind of error somehow:

local function add(a: number, b: number): number
	return a + b
end

add() -- attempt to perform arithmetic on a nil value (local 'a')

Perhaps TL could implement TypeScript's syntax for optional parameters, and assume that non-optional parameters are required?

local function dumbadd(x: number, y?: number): number
    y = y or 0
    return x + y
end

dumbadd() -- error: missing argument 'x'

pdesaulniers avatar Mar 15 '20 14:03 pdesaulniers

I have thought about this too. The "correct way" to deal with this would be via nilable types (or rather, non-nilable types), but that's a bit of a rabbit hole :).

One possibility indeed is to have a much more restricted form, which is merely about the syntactic presence of arguments in function calls (in your example, as in TypeScript, the ? is in y?, not in number?). I think it's worth giving this a try — I'll play a bit with this idea, thanks for the suggestion!

hishamhm avatar Mar 16 '20 02:03 hishamhm

The "correct way" to deal with this would be via nilable types (or rather, non-nilable types)

Yeah, that would be great! nil-related errors are so annoying :) However, as you said, that does seem complex to implement.

pdesaulniers avatar Mar 16 '20 03:03 pdesaulniers

The ? syntax you mention is sugar for a union with undefined:

const example_1 = (x: number, y?: number) => x + (y || 0)
const example_2 = (x: number, y: number | undefined) => x + (y || 0) 

I think the current union type in teal can already work here? All that remains is to add the "strict" mode similar to typescript's nullable support

Mehgugs avatar May 10 '20 08:05 Mehgugs

There is a lot more discussion about having nullable types in this TypeScript issue.

https://github.com/Microsoft/TypeScript/issues/13778

TypeScript does not make the return value of array accesses nullable, but this is a feature that many people still want. However it cannot be added to the language at this point without a complier flag, because it would break backwards compatibility.

In one of the FOSDEM talks for Teal the issue of having to make every array access nullable was mentioned.

I personally believe that making the return type of array accesses nullable early on will be better for type safety. The non-nullable types can be reserved for records. Some people say that not making array accesses nullable went against TypeScript's goal of preventing the user from forgetting things at compile time and not having them go looking for errors at runtime.

If a lot more type casting is needed then maybe there could be an operator like Kotlin's !! to indicate "I know what I'm doing." In the end Kotlin still had to add runtime null safety checks in order to make their nullable system work on top of the JVM.

Ruin0x11 avatar Sep 10 '20 01:09 Ruin0x11

Yeah, the trouble in both TypeScript and Teal's case is that it is designed as an extension to an existing language, where people expect to be able to migrate their projects.

I believe Kotlin is different enough so that it needs to interact with the JVM legacy but the code itself is actually new, right? (Kotlin being a language of its own and not a "Java dialect")

hishamhm avatar Sep 10 '20 19:09 hishamhm

Yes, it's true that Kotlin is not a subset of Java. My thought is that if table accesses in Lua can sometimes return nil, then modeling that in the type system makes it more intuitive since it indicates exactly what Lua might do underneath. It would at least bring the question of a null access up to the programmer wherever it could happen.

Also I think it would be very useful if SomeType? was equivalent to the union type SomeType | nil.

Ruin0x11 avatar Sep 12 '20 23:09 Ruin0x11

Currently writing a definitions file for a library that has overloaded functions of the shape function(number), function(number, number), etc up to a certain amount of arguments, and wishing there was special syntax for defining those, instead of overloading them by rewriting all valid signatures.

Guessing such syntax for type definitions overlaps strongly with the above discussion around implementation syntax, but bringing it up just in case.

Fang- avatar Oct 11 '20 20:10 Fang-

@Fang- As of Teal 0.8, since every type includes nil, if you specify as function(number, number, number, number, number) it will accept up to 5 numbers, so no need to overload as function(number), function(number, number)... (but note it will also accept f(nil, nil, 3, nil, 5)).

hishamhm avatar Oct 13 '20 15:10 hishamhm

1 being equal to a nil as a type check seems ultra-extremely dangerous

jordan4ibanez avatar Nov 05 '23 17:11 jordan4ibanez

My two cents:

One of the main benefits of a decent type system is some kind of nil-safety. Prepending a ? to a type to mark it as optional doesn't hurt to type (conversely, if backwards-compatibility is to be maintained, prepending a ! to a type to mark it as "mandatory" wouldn't hurt much either), and you gain a lot from it: The type system can tell you when you're not handling the nil case, or when you're passing nil to something that doesn't expect it. I'd even hazard a guess that accidental nils ending up where they shouldn't belong (the famous "attempt to ... a nil value") is one of the most frequent reasons of crashes - perhaps more frequent than accidentally passing the wrong type.
Doing this well isn't trivial however - you need to properly deal with guard clauses like if x == nil then return end as well as more subtle ones like assert(x) or even f(x) where f(x) does an assert(x).

Also, nil and "nothing" should not be confused; I would expect a "typed" Lua to distinguish the two, requiring exactly the specified number of arguments to be passed. It does happen that you forget an argument, and I'd expect a type system to catch this.

What about a different syntax for multiple "overloaded" signatures similar to what is used in the Lua reference manual? I'm thinking something like:

function(string, [number, string])
-- shorthand for the following 3 signatures:
function(string)
function(string, number)
function(string, number, string)

this could also be used for leading optional arguments:

function([string, number], string)
-- shorthand for the following 3 signatures:
function(string)
function(string, string)
function(string, number, string)

appgurueu avatar Nov 05 '23 19:11 appgurueu