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

Crazy idea (RFC): Function signature bindings #2023

Closed
haudan opened this issue Mar 2, 2019 · 2 comments
Closed

Crazy idea (RFC): Function signature bindings #2023

haudan opened this issue Mar 2, 2019 · 2 comments
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@haudan
Copy link

haudan commented Mar 2, 2019

Related to #1717

Short

Extend type with a new variant and properties for function signatures. Create a new type-builder expression fnsig yielding this new type variant. A fnsig describes the names and types of parameters and the return type.

Motivation

Consider this example of writing a program to dynamically execute a function from an array of function pointers (dispatch table).

const MathFunc = fn(i32) i32;

fn identity(x: i32) i32 {
    return x;
}

fn double(x: i32) i32 {
    return x * 2;
}

fn square(x: i32) i32 {
    return x * x;
}

pub fn main() void {
    const funcs = []const MathFunc {
        identity,
        double,
        square,
    };

    const input = i32(10);
    const output = funcs[chooseFunc()](input);
}

extern fn chooseFunc() usize;

Here, MathFunc is a binding of the type pointer to function of i32 => i32. As shown, function pointers can already be bound to a name.

Let's observe a more complicated example.

const MathFunc = fn(logger: ?*Logger, a: i32, b: i32, c: u8, terms: []const f64, sum_times: ?usize, approx_epsilon: f32) i32;

fn identity(logger: ?*Logger, a: i32, b: i32, c: u8, terms: []const f64, sum_times: ?usize, approx_epsilon: f32) i32 {
    ...
}
...

For every function from the dispatch table the entire function signature has to be typed out again every time, even though these functions are related and share the same signature. This creates a lot of redundant code.

With function signature bindings, the second example could be rewritten as

const MathFunc = fnsig(logger: ?*Logger, a: i32, b: i32, c: u8, terms: []const f64, sum_times: ?usize, approx_epsilon: f32) i32;

const identity = MathFunc {
    ...
};

const double = MathFunc {
    ...
};

const square = MathFunc {
    ...
};

pub fn main() void {
    const funcs = []const *const MathFunc {
        identity,
        double,
        square,
    };

    const output = funcs[chooseFunc()](...);
}

extern fn chooseFunc() usize;

Downsides

Obviously the usage of a binding completely hides the parameter types and names and return type. Inside the function body the parameter names would be derived from the names in the fnsig. This means bindings with anonymous parameters (const Foo = fnsig(i32, bool) u8) are not possible.

Less crazy

Here is a more useful instance of this feature, for declaring external functions.

const DumpAllState = fnsig(<obnoxiously long list of parameters>) void;

extern "jazzlib" stdcallcc jazz_dump_to_disk: DumpAllState;
extern "jazzlib" stdcallcc jazz_dump_as_log: DumpAllState;
extern "jazzlib" stdcallcc jazz_dump_to_dev_null: DumpAllState;

const ProcessAudio = fnsig(samples: [*]f32, count: usize) void;

extern "delayplugin" stdcallcc decent_delay: ProcessAudio;
extern "delayplugin" stdcallcc delay_with_reverb: ProcessAudio;
const myOwnVocoder = ProcessAudio { ... };

Not invented here

Good old C actually has this feature. Well, the less crazy variant at least. Most people are aware of function pointer aliases.

typedef void (*ApocalypseHandler)(int reason);
// Ways to cope with the apocalypse:
ApocalypseHandler handlers[] = {
    hide_under_desk,
    cry,
    time_travel
};

But C also has function type aliases.

typedef void ApocalypseHandler(int reason);
// Forward declaring ways to cope with the apocalypse:
ApocalypseHandler hide_under_desk;
ApocalypseHandler cry;
ApocalypseHandler time_travel;

// Use them
ApocalypseHandler *handlers[] = {
    hide_under_desk,
    cry,
    time_travel
};

// Define them
void cry(int reason) { ... }
@andrewrk andrewrk added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label Mar 2, 2019
@andrewrk andrewrk added this to the 0.5.0 milestone Mar 2, 2019
@andrewrk
Copy link
Member

Thanks for the proposal @Lisoph. I appreciate the way you laid out the problem/solution and following discussion. I think this does solve a particular problem, but ultimately, I'm going to make the call that the language complexity is not worth it. The price we pay for language simplicity is to have annoying redundancy and code verbosity sometimes.

@haudan
Copy link
Author

haudan commented May 6, 2019

@andrewrk thanks for the consideration, Andrew! I agree with your decision.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests

2 participants