Expressible By Function Literal

I got into an interesting issue where I wanted to initialise an Array with a ResultBuild, the way I ended up doing it, cause my Array to be continuously reinitialised. I therefor propose that we can conform our types to an ExpressibleByFunctionLiteral so I could turn my code from this:

var components: [GKComponentSystem<GKComponent>] = .init {
    NodeComponent.self
    MoveComponent.self
    VelocityComponent.self
}

To this

var components: [GKComponentSystem<GKComponent>] = {
    NodeComponent.self
    MoveComponent.self
    VelocityComponent.self
}

My thought would be that the conformance looks like this:

struct MyFunctionType: ExpressibleByFunctionLiteral {
   init(_ function: Input -> Return)
}

And each init signature should allow for the type to take different kinds of function literals.

5 Likes

What does the builder look like currently? It sounds weird that you'd need to reinitialize array every very often, and weirder that this pitch would help. Maybe we can go to another #swift-users thread to see if there's a better way for the builder.

It's not the builder that's the issue. It used to be just a computed property and that's why it go reinitialised about 60 times a second, and then I refactored it into what it is now which fixed the issue. This was just an example of what I needed, when I thought of the pitch.

The various bits you see here comes from the following:

@resultBuilder
struct SystemBuilder {
    static func buildBlock(_ components: GKComponent.Type...) -> [GKComponentSystem<GKComponent>] {
        components.map { GKComponentSystem(componentClass: $0) }
    }
    
    static func buildBlock(_ components: GKComponent...) -> [GKComponentSystem<GKComponent>] {
        components.map {
            let cs = GKComponentSystem(componentClass: type(of: $0))
            cs.addComponent($0)
            return cs
        }
    }
}

extension Array where Element: GKComponentSystem<GKComponent>
{
    init(@SystemBuilder components: () -> [Element]) {
        self = components()
    }
}

// MARK: - Systems
lazy var components: [GKComponentSystem<GKComponent>] = .init {
    NodeComponent.self
    MoveComponent.self
    VelocityComponent.self
}

lazy var systems: [GKComponentSystem<GKComponent>] = .init {
    MoveSystem(entityManager: self)
}

and can be found on this GitHub: GitHub - BastianInuk/SwiftPong: Pong Recreated in Swift to learn and teach SpriteKit and GameplayKit

But again, I don't believe the bug I made could be fixed with Function literals, I only think it would add a little syntax sugar over my own fix.

Looking at the pitch, since generic conforms to protocol only once. You'd be locked to having only [GKComponentSystem<GKComponent>] be EBFL. It doesn't look very versatile.

PS

Note that you can make a builder that initializes the array only at the very end (buildResult). Admittedly, it's much more complex than the naive implementation.

There's also some discussion about adding ArrayBuilder after ResultBuilder landed, we'd probably need more use cases to know how to design it properly.

1 Like

I tinkered a little with it, and ended up making my own protocol, it's not designed to be good and not going into this specific project either, but I did make something which worked.

protocol ExpressibleByFunctionLiteral {
    associatedtype Input
    associatedtype Output
    associatedtype Function = (Input) -> Output
    
    init(_ function: Function )
}

There's an issue here where it won't infer Input and Output based on init, not sure if that's intended behaviour. I then made an operator (with a throwaway operator)

infix operator <| : AssignmentPrecedence
func <|<Type: ExpressibleByFunctionLiteral>( value: inout Type?, function: Type.Function) {
    value = Type.init(function)
}

And conform my very specific usecase like this, with the operator as static because I needed the @SystemBuilder for my usecase:

extension Array: ExpressibleByFunctionLiteral where Element: GKComponentSystem<GKComponent>
{
    typealias Input = ()
    
    typealias Output = [Element]
    
    init(_ function: () -> [Element]) {
        self = function()
    }
    
    @discardableResult
    static func <|( value: inout Self, @SystemBuilder function: Function) -> Self {
        value = .init(function)
        return value
    }
}

There's two issues with this. inout doesn't take an uninitialised variable, not even if it's not read, and that seems like by design from a different thread (I'll link it in when I found it). And = is not overloadable, so I can't just write 100% my own Literal protocol.

Using typealias for Function should do the trick.

I know this is more in the #swift-users, but I'm curious how this works!

On a different note: Adding ExpressibleByFunctionLiterals could also help get us closer to C++ interop, so we can initialise std::function from Swift.

It's just that associatedtype Function = ... does not require any relationship between Function and (Input) -> Output, just default to (Input) -> Output. It could easily be Int.

typealias just fixes Function to (Input) -> Output, so that the compiler has something to use.

I have a potential use case for this, at least in parameters.

I have an Action type which wraps a protocol

protocol ActionProtocol {
    func performAction(on state:State)->State
}

struct Action:ActionProtocol {
    private action:ActionProtocol

    init(_ action: ActionProtocol) {
        self.action = action
    }

    func performAction(on state:State)->State {
        action.performAction(on: state)
    }
}

The reason I have the struct wrapper is that I can conform it to ExpressibleBy... and attach static vars/funcs to it for easy use:

myFunc(state: myState, action: [.myAction1, .myAction2]) /// The array makes an action which performs a sequence of subactions

Right now, for a custom action defined by a closure, I either have to make a separate closure version of each function which takes an action, or make a static func wrapper .custom({…})

Not the worst thing in the world, but it would be nice to have ExpressibleAsFunction to allow {…} to just work™ the same way […] does for a sequence of actions...

Edit: In an ideal world, it would allow trailing closure syntax for the last parameter:

myFunc(state: myState) { state in
     ///Custom Action logic here
}
2 Likes
Terms of Service

Privacy Policy

Cookie Policy