Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lambdas to E2 #2829

Merged
merged 10 commits into from
Nov 13, 2023
Merged

Add lambdas to E2 #2829

merged 10 commits into from
Nov 13, 2023

Conversation

Vurv78
Copy link
Contributor

@Vurv78 Vurv78 commented Nov 9, 2023

Lambdas are anonymous functions that can be used as expressions, and have upvalues.

They are type erased but store their types at runtime, similar to tables.

These are the second part of the transition away from the trigger system.

Fixes #1677

Syntax

They're TS-style ES5 anonymous functions.
Arrow functions could potentially be added in the future.

Nicest thing about this syntax is there's no explicit return type needed, compiler can see the return type itself. Could bring this functionality to regular functions in the future.

const IsEven = function(N:number) {
    return N % 2 == 0
}

const Not = function(N:number) {
    return !N
}

const IsOdd = function(N:number) {
    return Not(IsEven(N)[number])[number]    
}

print(IsOdd(2)[number])

Op costs

Costs 15 ops to call, 25 to create.

How they work

The implementation is pretty simple, just stores the current scopes whenever the lambda is made, and set the current scopes to that when it is called.

Caveats

Variadic parameters currently aren't allowed and won't be for the foreseeable future, as to not slow down calls. Maybe this could be done in the future but I don't see it as worth it right now

function(...A:array) {}

This will give you a compile error

Developer Representation

They are stored as a table with a metatable.

Here's a basic coroutine example

e2function function coroutineWrap(function f)
    local fn = f:Unwrap("", self)
    return E2Lib.Lambda.new("", f.ret, coroutine.wrap(fn))
end

e2function void coroutineYield()
    coroutine.yield()
end

And the emmylua annotations

---@class E2Lambda
---@field fn fun(args: any[]): any
---@field arg_sig string
---@field ret string
local Function = {}
Function.__index = Function

function Function.new(args, ret, fn)
	return setmetatable({ arg_sig = args, ret = ret, fn = fn }, Function)
end

E2Lib.Lambda = Function

--- Call the function without doing any type checking.
--- Only use this when you check self:Args() yourself to ensure you have the correct signature function.
function Function:UnsafeCall(args)
	return self.fn(args)
end

function Function:Call(args, types)
	if self.arg_sig == types then
		return self.fn(args)
	else
		error("Incorrect arguments passed to lambda")
	end
end

function Function:Args()
	return self.arg_sig
end

function Function:Ret()
	return self.ret
end

--- If given the correct arguments, returns the inner untyped function you can call.
--- Otherwise, throws an error to the given E2 Context.
---@param arg_sig string
---@param ctx RuntimeContext
function Function:Unwrap(arg_sig, ctx)
	if self.arg_sig == arg_sig then
		return self.fn
	else
		ctx:forceThrow("Incorrect function signature passed, expected (" .. arg_sig .. ") got (" .. self.arg_sig .. ")")
	end
end

Other changes

On @strict global variables won't be reset, this is to fix this code not working properly

local X = 5
timerSimple(1, function() { print(X) }) # Would print 0 since E2 would reset X, since it's in the "global" scope.

I don't think this should affect people since you really shouldn't have been able to use variables before they are set, and the alternative would be a large change to how variables are stored which I don't think is worth it. Either way it's only for @strict.

String calls

String calls have been deprecated, and will soon be an error on @strict.

String calls now cost 25 ops to call, I might have missed this in the compiler rewrite, not entirely sure.

* Functions are now tables, containing parameter signature, return type and inner function
* function:getParameterTypes()
* function:getReturnType()
* Don't reset global variables on `@strict`, fixing issue with top level locals not working as upvalues since they'd be reset by the runtime. I don't think this would actually affect anyone since you shouldn't be able to use variables before they're assigned, but it's `@strict` only behavior anyway.
@Vurv78
Copy link
Contributor Author

Vurv78 commented Nov 9, 2023

Note timers and other libraries that'd make use of lambdas are not implemented in this PR. They will come in separate PRs. Implementations should be fairly small/easy

@Vurv78
Copy link
Contributor Author

Vurv78 commented Nov 9, 2023

Only problem right now is that the editor does not like the syntax and highlights a lot of things incorrectly

I'd already fixed this bug with functions that have return values but this was not fixed for functions without return values. Also added a test case for this.
@Vurv78
Copy link
Contributor Author

Vurv78 commented Nov 9, 2023

local SayMessage = function() {}

const SetMessage = function(Message:string) {
	SayMessage = function() {
		return Message
	}
}

SetMessage("There's a snake in my boot!")

assert( SayMessage()[string] == "There's a snake in my boot!" )
assert( SayMessage()[string] == "There's a snake in my boot!" )

SetMessage("Reach for the sky!")

assert( SayMessage()[string] == "Reach for the sky!" )

* Add tests to ensure variadic parameters, void parameters and implicit parameters aren't allowed

* Fix lambdas potentially not returning at all codepaths like functions now are expected to do. Added a test for that.
@Denneisk
Copy link
Member

Could it be possible to get references to E2functions (without wrapping them using E2)? Probably too much for this PR but I wanted to have references to the easing functions.

@Vurv78
Copy link
Contributor Author

Vurv78 commented Nov 11, 2023

Can be done later, maybe alongside lowercase variables

I should stop prematurely optimizing this.. This is like the third or second time I've made this mistake lol
Actually a really easy fix. Surprising
@Vurv78 Vurv78 merged commit 3522df9 into wiremod:master Nov 13, 2023
1 check failed
@Vurv78 Vurv78 deleted the functions branch November 13, 2023 00:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support closures in E2
2 participants