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

support clearer "method" syntax on structs? #746

Closed
jfo opened this issue Feb 6, 2018 · 7 comments
Closed

support clearer "method" syntax on structs? #746

jfo opened this issue Feb 6, 2018 · 7 comments
Labels
Milestone

Comments

@jfo
Copy link
Sponsor Contributor

jfo commented Feb 6, 2018

My basic feeling here is that calling a function using dot syntax on an instance of a struct is a method call. We're already implicitly passing self but forcing explicit arity on self at the definition site. Would it be unreasonable to introduce a meth keyword to delineate implicit self passing from function namespacing? It would only be available inside a struct. I realize this makes structs act like classes in a way, but it is clear that this is an emergent pattern and making that use explicit would be clearer in many cases. Furthermore, disallowing method calls on struct names and function calls on instances at the call site would codify what is already an implicit ruleset.

This is syntax related to OOP but not to vtables or polymorphism, which are different issues. Clearer object-ish calling conventions might make that easier to deal with as well, conceptually.

I'm not necessarily advocating for more object orientation, I'm just talking about some things that feel counterintuitive to me in the status quo.

pub const pubStruct = struct {
    pub fn pubStatic() u8 { return 1; }
    fn privStatic() u8 { return 2; }

    pub fn pubInstance(self: pubStruct) u8 { return 3; }
    fn privInstance(self: pubStruct) u8 { return 4; }

    pub const pubThing:u8 = 5;
    const privThing:u8 = 6;
};

fn main() void {
    // ok, like a static method, namespaced, basically.
    _ = pubStruct.pubStatic();    // 1

    // why isn't this internal to the struct? `pub` seems to not matter here.
    _ = pubStruct.privStatic();   // 2

    // `self` is implicitly passed when called on an instance, why am I allowed
    // to even call this here though? shouldn't this be an error when called on
    // the struct ns/type?
    _ = pubStruct.pubInstance();  // error: expected 1 arguments, found 0

    // same but + private not having meaning here
    _ = pubStruct.privInstance(); // error: expected 1 arguments, found 0

    // again, private v public...
    _ = pubStruct.pubThing;       // 5
    _ = pubStruct.privThing;      // 6

    const mypub: pubStruct = undefined;

    // self is implicitly passed in the case of "static" struct functions, I
    // feel like I shouldn't be able to do this at all instead of getting an arity
    // error
    _ = mypub.pubStatic();    // error: expected 0 arguments, found 1
    _ = mypub.privStatic();   // error: expected 0 arguments, found 1

    // these are essentially instance methods, but I can still call both public
    // and non-public ones.
    _ = mypub.pubInstance();  // 3
    _ = mypub.privInstance(); // 4

    // but I can't use "field access" to get at scoped vars... (this is as I would expect)
    _ = mypub.pubThing;       // error: no member named 'pubThing' in struct 'pubStruct'
    _ = mypub.privThing;      // error: no member named 'privThing' in struct 'pubStruct'

}
@jfo jfo changed the title support clearer "method"s syntax on structs? support clearer "method" syntax on structs? Feb 6, 2018
@andrewrk andrewrk added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label Feb 6, 2018
@andrewrk andrewrk added this to the 0.3.0 milestone Feb 6, 2018
@Hejsil
Copy link
Sponsor Contributor

Hejsil commented Feb 7, 2018

There is an advantage to functions and "methods" having the same syntax, which is, that they can be copypasted between global scope and struct scope, and nothing needs to changes (refactorability).

Many of the comments seem to be related to just needing better errors for these cases. I totally agree that the compiler shouldn't think I'm trying call "methods" from the type, and static functions from the instance.

As for pub, maybe things should be private to their current namespace instead of private to the file they are in. I'd like for pub to be simple, so hopefully, no public, protected, private, internal thingy.

@tiehuis
Copy link
Member

tiehuis commented Feb 7, 2018

@Hejsil

This is generally how I assumed things worked and how I declare my classes. I would generally be in favour of the most obvious approach being the one used.

One small detail that needs to be considered is how testing behaves in conjunction with internal struct functions. These private details would be untestable with more limited scoping rules which may be an issue as tests currently are not allowed in struct definitions. An alternative would be to allow test to occur in struct/enum definitions to get around this.

@jfo
Copy link
Sponsor Contributor Author

jfo commented Feb 7, 2018

@Hejsil I agree with that thought on refactorability and also that mostly I'm concerned about the error messaging. I find it strange that I can call "static" functions with the dot syntax as well as access members of the struct (and thereby call functions that are a member without passing self)

I have some more thoughts on this but here's a snippet I was thinking about if we had anon functions and default struct initialization params:

const warn = @import("std").debug.warn;

fn func(x:u8) u8 {
    return x;
}

const Thing = struct {
    // could be default to func which could live in the container Thing scope
    // here or potentially be anonymous
    f: (fn(u8) u8) // = func,
};


test "run" {
    const s = Thing { .f = func };
    warn("\n{}\n", s.f(8)); // note that this doesn't pass self
    warn("\n{}\n", Thing.f(9)); // error: container 'Thing' has no member called 'f'
}

@Hejsil
Copy link
Sponsor Contributor

Hejsil commented Feb 7, 2018

Maybe we need another syntax for calling methods. instance->method()? 🤔

@andrewrk
Copy link
Member

One small detail that needs to be considered is how testing behaves in conjunction with internal struct functions.

One idea is that we already have the concept of a "test build". And in this build mode, everything is public.

@raulgrell
Copy link
Contributor

I'm fairly satisfied with status quo, though I agree error messages could be improved. Is this a clearer explanation of what's going on? Disregard visibility for now.

const assert = @import("std").debug.assert;

const S = struct {
    a_member: u8,
    a_member_func: fn() u8,
    a_member_method: fn(self: &const S) u8,
   
    const a_namespaced_val: u8 = 11;
    const a_namespaced_func = aNamespacedFunction;

    fn aNamespacedFunction() u8 { return 42; }
    fn aNamespacedMethod(self: &const S) u8 { return self.a_member; }
};

fn aRegularFunction() u8 { return 22; }
fn aStructFunction(self: &const S) u8 { return self.a_member * 2; }

test "namespacing" {
    const s = S {
        .a_member = 33,
        .a_member_func = aRegularFunction,
        .a_member_method = aStructFunction  
    };


    // Namespaced vals are only available through the struct type
    assert(S.a_namespaced_val == 11);
    assert(S.a_namespaced_func() == 42);

    // assert(s.a_namespaced_val == 11); // error: no member named 'a_namespaced_val' in struct 'S'
    // assert(s.a_namespaced_func() == 42); // error: no member named 'a_namespaced_val' in struct 'S'


    // Members are only available through instances of the struct
    assert(s.a_member == 33);
    assert(s.a_member_func() == 22);
    assert(s.a_member_method(s) == 66);

    // assert(S.a_member == 33); // container 'S' has no member called 'a_member'
    // assert(S.a_member_func() == 22); // container 'S' has no member called 'a_member_func'
    // assert(S.a_member_method(s) == 66); // container 'S' has no member called 'a_member_method'


    // Functions are sort of available through both
    assert(S.aNamespacedFunction() == 42);
    assert(S.aNamespacedMethod(s) == 33);
    assert(s.aNamespacedMethod() == 33);


    // Method call syntax is only available when the function is in the namespace of the
    // instance type and the first parameter is of that same type.

    // Here there is no instance
    //assert(S.aNamespacedMethod() == 33); // error: expected 1 arguments, found 0
    
    // Here the function is not in the namespace
    //assert(s.aStructFunction() == 33); // error: no member named 'aStructFunction' in struct 'S'
    //assert(s.a_member_method() == 66); // error: expected 1 arguments, found 0    
    
    // Here the function does not take an instance
    //assert(s.aNamespacedFunction() == 42); // error: expected 0 arguments, found 1
}

@andrewrk andrewrk modified the milestones: 0.3.0, 0.4.0 Feb 28, 2018
@andrewrk andrewrk added question and removed proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. labels Nov 21, 2018
@andrewrk
Copy link
Member

andrewrk commented Nov 21, 2018

I think I mistakenly labeled this a proposal. What's going on here is that early on in the language, I added pub syntax for top level declarations and fields, and then never quite got around to implementing most of the enforcement needed to make it meaningful.

And then along the way I've become more in favor of public things, especially struct fields. I certainly think that test builds at least should have full access to all private things.

What's needed is a comprehensive proposal that really considers everything and prescribes a full description of how public/private stuff should work in Zig, and then we can talk about that proposal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants