Going to the Google's walled garden with Go



  • @cvi said in Going to the Google's walled garden with Go:

    FWIW- I tried finding something to back the claim of Go's duck typing being a performance hazard briefly, but didn't find anything. I don't do Go, so I might not have searched for the right things. Links appreciated.

    You didn't find any, because it isn't a performance hazard.

    It does lead to a quirk that instead of putting the pointer to the virtual method table in the object—which does not need it since dispatch on concrete types is never virtual—the interface-typed pointers are ‘fat’, carrying separate pointer to the interface definition and to the data.

    But that might even be faster than the traditional approach. Because as far as I know JVM and CLR don't adjust the pointer to the object (like C++ does), so they have to deal with the vtable being different in different implementations of the interface. But in fat pointer the vtable is always the expected layout, so there are no differences to deal with.



  • @Bulb said in Going to the Google's walled garden with Go:

    @Mason_Wheeler said in Going to the Google's walled garden with Go:

    @Bulb said in Going to the Google's walled garden with Go:

    @Mason_Wheeler Inheritance didn't make OO a world-conquering paradigm, it is being abandoned as a huge mistake.

    Gotta love the passive voice there. Who exactly is proclaiming it "a huge mistake" and abandoning it? Not the people producing serious projects that deal with significant levels of complexity!

    Those don't proclaim anything, they are just not using much inheritance any more. The newer code I've seen, and newer frameworks I've seen, are all interfaces, dependency injection and attributes, but almost no actual inheritance. Except in UI, it is still holding out there occasionally.

    Ugh. Dependency injection. An ugly Java solution to an ugly Java problem. Delphi had an enormously better solution way back in 1995, and I really wish Anders Hejlsberg had brought it with him when Microsoft had him build a framework that was about 70% Delphi rewritten to look like a better Java.

    The critique of it does not come from either C++ of FP folks, it comes from the advanced Java and C# programmers who are finally realizing it is just a special variant of composition and that the code ends up being easier to understand and maintain if normal composition is used instead.

    The "prefer drills over hammers" argument again? Seriously? :rolleyes:

    Inheritance is not composition. Composition is not inheritance. A hammer is not a drill. A drill is not a hammer. Anyone attempting to equate the two or assert that one is substitutable for the other is :crazy: and should be dismissed as such without further discussion.

    Inheritance does two things: bring the members of the base into the object, which is composition,

    No it's not. Composition means you can put a Foo and a Bar together to make a Baz. Inheritance means that Bar can inherit from Foo and be treated as a genuine Foo anywhere that the code asks for a Foo. Composition can not do this, and it's insanely useful in managing complexity.

    And I don't agree Go does interfaces so poorly either. The ‘static duck typing’ approach makes things somewhat prone to conflicts, which is a general theme in Go, but at least it gets one thing right: it allows extending existing types with new interfaces.

    That's nowhere near "right." One reason it's insanely wrong becomes apparent the minute you consider the existence of ambiguous words: that Foo method on type Bar doesn't mean the type of Fooing that the interface creator intended!

    Like in any duck-typed system, the user is responsible for not casting the object to that interface if the method with matching signature does not actually follow the desired semantics. I am not big fan of that bit.

    Like I said: completely broken.

    The other reason is that duck typing of any kind is unperformant, and this is one of the major reasons why Go code tends to be slow.

    Except it isn't, because this is static. C++ does exactly the same thing in templates, does it a lot there, and is still the fastest of the bunch.

    Nope. See above, re: wrapping. C++ does something extremely different, because it's actually fully static. This isn't.

    The thing is, the promise of great code reuse never materialized in Java the way it was envisioned and hoped for.

    The people doing the envisioning back in the 90s had never heard of package managers. So we didn't get what they envisioned; instead we got something even better.

    But people claim the code reuse did materialize in Julia. Julia also has methods defined outside the types they operate on

    So does C#. It's called "extension methods" and, among other things, it's what makes stuff like LINQ possible.

    No, it does not. Extension methods allow adding methods to interfaces, but as far as I can tell they don't allow taking type Foo from library and stating that from now on it also implements interface Baz like this. And then taking type Bar from library and also stating from now on it implements interface Baz like that.

    Oh, that's a feature currently under development by the .NET guys. It's supposed to be here Real Soon Now ™ as soon as they get a few technical issues worked out.



  • @Mason_Wheeler said in Going to the Google's walled garden with Go:

    @cvi said in Going to the Google's walled garden with Go:

    @Mason_Wheeler said in Going to the Google's walled garden with Go:

    The other reason is that duck typing of any kind is unperformant

    Really? Looking at the way Go's does duck typing seems to be implementable efficiently, certainly without much more overhead compared to other implementations of virtual functions. (FWIW- I tried finding something to back the claim of Go's duck typing being a performance hazard briefly, but didn't find anything. I don't do Go, so I might not have searched for the right things. Links appreciated.)

    In addition, C++ essentially allows for duck typing w.r.t. templated types. Calls are resolved at compile-time, so there's no run-time overhead. In fact, it guarantees that inlining is possible, so it's even possible to skip the overhead of the function call in the first place.

    You can use the above mechanism to get something like Go's interfaces. Run-time overhead is then pretty much that of an indirect function call (which can potentially be improved on by LTO / de-virtualization, if you build with those enabled).

    Not really. Types are not declared as implementing an interface. This means that at any place where an interface (rather than a concrete type) is passed as a parameter (or exists as a member of a struct) the code using it has no idea which actual calls are being made.

    Well, yes, it's dynamic dispatch. But that's the case with interfaces in all languages, even if the types are explicitly declared as implementing the interfaces. The code that only got an interface does not know which of the concrete types that implement it it will get, so it has to dispatch dynamically.

    To make this function on a bits-and-bytes level you need to wrap the actual value in a wrapper which makes everything indirect and adds memory overhead to boot.

    It is a trade-off. It adds memory overhead, but then it can dispatch by two completely dumb dereferences because the vtable layout is always as expected. C++ uses just one pointer, but the object contains separate vtable pointer for each interface and the pointer to the object is adjusted to actually point to the right vtable, which in turn requires that the functions in the vtable are wrappers that undo that adjustment.
    As far as I know JVM and CLR don't adjust the pointer, but that means they have to look up the right part of the vtable. So each approach is more efficient in some places and less in others.

    (And also explains how you can have a non-nil interface that's actually a nil value, which is still a massive :wtf:!)

    Does it, though? The comparison with nil should say it's nil if the data pointer is nil even if the vtable pointer isn't. Did you actually see do something else?



  • @Bulb said in Going to the Google's walled garden with Go:

    @Mason_Wheeler said in Going to the Google's walled garden with Go:

    @cvi said in Going to the Google's walled garden with Go:

    @Mason_Wheeler said in Going to the Google's walled garden with Go:

    The other reason is that duck typing of any kind is unperformant

    Really? Looking at the way Go's does duck typing seems to be implementable efficiently, certainly without much more overhead compared to other implementations of virtual functions. (FWIW- I tried finding something to back the claim of Go's duck typing being a performance hazard briefly, but didn't find anything. I don't do Go, so I might not have searched for the right things. Links appreciated.)

    In addition, C++ essentially allows for duck typing w.r.t. templated types. Calls are resolved at compile-time, so there's no run-time overhead. In fact, it guarantees that inlining is possible, so it's even possible to skip the overhead of the function call in the first place.

    You can use the above mechanism to get something like Go's interfaces. Run-time overhead is then pretty much that of an indirect function call (which can potentially be improved on by LTO / de-virtualization, if you build with those enabled).

    Not really. Types are not declared as implementing an interface. This means that at any place where an interface (rather than a concrete type) is passed as a parameter (or exists as a member of a struct) the code using it has no idea which actual calls are being made.

    Well, yes, it's dynamic dispatch. But that's the case with interfaces in all languages, even if the types are explicitly declared as implementing the interfaces. The code that only got an interface does not know which of the concrete types that implement it it will get, so it has to dispatch dynamically.

    The difference is that the type isn't declared to implement the interface, so you can't do what C# and Java do and put the interface data on the type itself. So you end up with a wrapper around the type instead, which adds memory overhead in addition to the indirection overhead.

    To make this function on a bits-and-bytes level you need to wrap the actual value in a wrapper which makes everything indirect and adds memory overhead to boot.

    It is a trade-off. It adds memory overhead, but then it can dispatch by two completely dumb dereferences because the vtable layout is always as expected. C++ uses just one pointer, but the object contains separate vtable pointer for each interface and the pointer to the object is adjusted to actually point to the right vtable, which in turn requires that the functions in the vtable are wrappers that undo that adjustment.
    As far as I know JVM and CLR don't adjust the pointer, but that means they have to look up the right part of the vtable. So each approach is more efficient in some places and less in others.

    Not sure what you mean by "adjust the pointer." Can you elaborate?

    (And also explains how you can have a non-nil interface that's actually a nil value, which is still a massive :wtf:!)

    Does it, though? The comparison with nil should say it's nil if the data pointer is nil even if the vtable pointer isn't. Did you actually see do something else?

    Yes. This :wtf: caused grief for me multiple times, working with code in Go's standard library (ie. not random third-party garbage) while writing production Go code for a Fortune 500 company. The problem is a very real one, not theoretical at all.



  • @Bulb said in Going to the Google's walled garden with Go:

    But in fat pointer the vtable is always the expected layout, so there are no differences to deal with.

    And, depending on the implementation, you save yourself an indirection by not having to chase through the additional vtable pointer to find the function pointers in there. (Assuming you have to materialize the interface data in memory in the first place.)



  • @Mason_Wheeler said in Going to the Google's walled garden with Go:

    @Bulb said in Going to the Google's walled garden with Go:

    Inheritance does two things: bring the members of the base into the object, which is composition,

    No it's not. Composition means you can put a Foo and a Bar together to make a Baz. Inheritance means that Bar can inherit from Foo and be treated as a genuine Foo anywhere that the code asks for a Foo. Composition can not do this, and it's insanely useful in managing complexity.

    This is a bit of termitological mismatch then. I include Foo includes instance of Bar under composition.

    And for treating Bar as Foo, that's exactly the part that usually runs afoul of the Liskov substitution principle, meaning it is not so completely true and you can only treat it as such where you need the Faa and Fee interfaces. At which point you are better off with including Foo in Bar and implementing Faa and Fee by delegating to the member.

    Nope. See above, re: wrapping. C++ does something extremely different, because it's actually fully static. This isn't.

    You claimed it's a problem with the duck typing, but it has nothing to do with duck typing. It is dynamic dispatch, but that's about the same as dynamic dispatch anywhere else.

    No, it does not. Extension methods allow adding methods to interfaces, but as far as I can tell they don't allow taking type Foo from library and stating that from now on it also implements interface Baz like this. And then taking type Bar from library and also stating from now on it implements interface Baz like that.

    Oh, that's a feature currently under development by the .NET guys. It's supposed to be here Real Soon Now ™ as soon as they get a few technical issues worked out.

    Good to hear. That will be a useful feature. It will be difficult to add to the runtime though, because it will require tweaking the vtables and making sure it does not break anything—or using fat pointers.

    We started with comparing it to Java though, so unless you heard Java is planning the same, the argument stands.



  • @topspin said in Going to the Google's walled garden with Go:

    JS does prototypical inheritance, for example.

    You could actually sort of do something that looked like a class and smelt like a class by pretending a function was a class and fudging it with adding inherited methods.

    But JS does actual classes now, as well as prototype inheritance. It even has private properties and methods and everything.

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes in case anyone is curious at what fuckery this now is.



  • @Arantor JavaScript started out as a simple language with a lightweight runtime, which was a worthy goal. Then people started creating higher level languages that would compile down to it, and that looked like a good idea. But instead of settling on one or two of those, the browser developers started pushing the features down to JS, so now it is no longer either simpler or lightweight, but the higher level features can't be properly designed, because they have to be bolted on in a backward compatible manner, so it's a mess instead.


  • BINNED

    @Arantor I know, but the point was that class-based OOP isn’t the only possible way to do OOP. Which you should at least acknowledge if you treat OOP as the best thing since sliced bread.


  • Discourse touched me in a no-no place

    @Mason_Wheeler said in Going to the Google's walled garden with Go:

    Dependency injection. An ugly Java solution to an ugly Java problem. Delphi had an enormously better solution way back in 1995

    Out of pure curiosity, what was that "enormously better solution"? I never used any of the OO extensions to Pascal back in that period. (In 1995 I would have been swearing at the world's most awful and slow implementation of CLOS while discovering that scripting languages were much faster.)


  • Discourse touched me in a no-no place

    @Mason_Wheeler said in Going to the Google's walled garden with Go:

    working with code in Go's standard library (ie. not random third-party garbage)

    Oh, that'd be random second-party garbage then! 😉



  • @Mason_Wheeler said in Going to the Google's walled garden with Go:

    @Bulb said in Going to the Google's walled garden with Go:

    Does it, though? The comparison with nil should say it's nil if the data pointer is nil even if the vtable pointer isn't. Did you actually see do something else?

    Yes. This :wtf: caused grief for me multiple times, working with code in Go's standard library (ie. not random third-party garbage) while writing production Go code for a Fortune 500 company. The problem is a very real one, not theoretical at all.

    Then it's even bigger shit than I thought.

    I would still like to know how such thing came into existence. A reference that is not nil, but cannot be used, shouldn't be created in the first place.



  • @dkf said in Going to the Google's walled garden with Go:

    @Mason_Wheeler said in Going to the Google's walled garden with Go:

    Dependency injection. An ugly Java solution to an ugly Java problem. Delphi had an enormously better solution way back in 1995

    Out of pure curiosity, what was that "enormously better solution"? I never used any of the OO extensions to Pascal back in that period. (In 1995 I would have been swearing at the world's most awful and slow implementation of CLOS while discovering that scripting languages were much faster.)

    Delphi has a metaclass system that solves the same basic problems as DI in a much more static (and therefore both more performant and more verifiably correct) fashion.


  • Discourse touched me in a no-no place

    @Mason_Wheeler said in Going to the Google's walled garden with Go:

    Delphi has a metaclass system that solves the same basic problems as DI in a much more static (and therefore both more performant and more verifiably correct) fashion.

    I don't quite see how that could work. How would the metaclass get to know more about the world of objects, or solve the instantiation order problem?



  • @dkf Not sure what you're referring to here... 😕


  • Discourse touched me in a no-no place

    @Mason_Wheeler Perhaps a simple example will help? (This is trimmed down from real code by leaving out a bunch of stuff that isn't to do with this discussion because the whole stuff to do with actually building an email is boring.)

    @Component
    public class ReportMailSender {
    	private static final Logger log = getLogger(ReportMailSender.class);
    
    	@Autowired
    	private JavaMailSender emailSender;
    
    	@Autowired
    	private ReportProperties props;
    
    	public void sendMessage(String body) {
    		var message = new SimpleMailMessage();
    		message.setFrom(props.getFrom());
    		message.setTo(props.getTo());
    		message.setSubject(props.getSubject());
    		message.setText(body);
    		try {
    			emailSender.send(message);
    		} catch (MailException e) {
    			log.warn("problem when sending email", e);
    		}
    	}
    }
    

    For the DI/IoC to work, I stick @Autowired on a few fields and label the class as a @Component. That's it. The two things injected are:

    1. Validated configuration properties. I don't get them at all if there's an error in the config; the user gets a boot-time error. (The configuration is its own component. I'm deliberately choosing something from the middle of the stack of stuff.)
    2. The framework component that can send built messages. Sending emails is a grotty business, and I'm very happy to not have to write a secure SMTP client.

    What I don't grasp is how a metaclass would help. It's not a difficult class to create an instance of this class in the first place, but there are things you need to set it up with for it to work right.

    (Yes,. I could have used constructor injection in this case. I didn't.)



  • @dkf said in Going to the Google's walled garden with Go:

    What I don't grasp is how a metaclass would help.

    Hm, the thing that processes the annotations (like @Component and @Autowired) is called a metaclass in many languages. So … it's probably pretty much the same thing under different names. Maybe some of the logic is done at compile time rather than class load time, so it fails earlier, but that's about it.


  • Discourse touched me in a no-no place

    @Bulb said in Going to the Google's walled garden with Go:

    Hm, the thing that processes the annotations (like @Component and @Autowired) is called a metaclass in many languages.

    OK, except it's got to have a view of all sorts of things. I think of it as framework rather than as a metaclass precisely because it isn't solving how to build a single object but rather how to build an application. (I think a simple factory is, or rather should be, a metaclass.)

    Maybe some of the logic is done at compile time rather than class load time, so it fails earlier, but that's about it.

    In theory you could have some sort of manifest determined at compile time so that you don't need to scan for things to do or puzzle away at the ordering of them; those are almost certainly the parts that are expensive right now. Manifest generation would be something that it is fairly easy to add into the toolchain. OTOH, it would also be one more thing to go wrong in ways that make development different from testing and/or deployment...



  • @dkf said in Going to the Google's walled garden with Go:

    @Bulb said in Going to the Google's walled garden with Go:

    Hm, the thing that processes the annotations (like @Component and @Autowired) is called a metaclass in many languages.

    OK, except it's got to have a view of all sorts of things. I think of it as framework rather than as a metaclass precisely because it isn't solving how to build a single object but rather how to build an application. (I think a simple factory is, or rather should be, a metaclass.)

    A metaclass is, generally speaking, something that injects extra code in a class as it is compiled or loaded. It is just the part of the DI framework, the one that prepends the injection code to the constructor.


  • Discourse touched me in a no-no place

    @Bulb said in Going to the Google's walled garden with Go:

    @dkf said in Going to the Google's walled garden with Go:

    @Bulb said in Going to the Google's walled garden with Go:

    Hm, the thing that processes the annotations (like @Component and @Autowired) is called a metaclass in many languages.

    OK, except it's got to have a view of all sorts of things. I think of it as framework rather than as a metaclass precisely because it isn't solving how to build a single object but rather how to build an application. (I think a simple factory is, or rather should be, a metaclass.)

    A metaclass is, generally speaking, something that injects extra code in a class as it is compiled or loaded.

    There are several different definitions. I think a true metaclass is an entity which conceptually subclasses the class of classes, and so can customise the behaviour of new (at the stages prior to calling the constructor). Not all languages allow you to explicitly do that, but where you can do so you don't use anything like as many funky factories and stuff like that.



  • @dkf Oh wow. If people are using DI frameworks to do stuff like that now, it's getting even worse! 🤢



  • @Mason_Wheeler This is nothing. I’ve worked with someone who made a library that allows other IOC libraries to be plugged in on the fly. And had some fairly sophisticated features that were hard to understand and offered very little utility. This was his entire work for a year. He gives talks at big conferences now where they have a hardon for “Domain Driven Development”

    It’s like one gets paid for needlessly adding complexity where none exists. Oh wait…


  • Considered Harmful

    @dkf said in Going to the Google's walled garden with Go:

    @Bulb said in Going to the Google's walled garden with Go:

    @dkf said in Going to the Google's walled garden with Go:

    @Bulb said in Going to the Google's walled garden with Go:

    Hm, the thing that processes the annotations (like @Component and @Autowired) is called a metaclass in many languages.

    OK, except it's got to have a view of all sorts of things. I think of it as framework rather than as a metaclass precisely because it isn't solving how to build a single object but rather how to build an application. (I think a simple factory is, or rather should be, a metaclass.)

    A metaclass is, generally speaking, something that injects extra code in a class as it is compiled or loaded.

    There are several different definitions. I think a true metaclass is an entity which conceptually subclasses the class of classes, and so can customise the behaviour of new (at the stages prior to calling the constructor).

    That's still too restrictive. A metaclass' instances are classes, so it can be used to query and modify anything about an existing class, be it constructors, regular methods or attributes, or even create classes on the fly, at any time. Of course that's subject to the restrictions of your language runtime so all of it is rarely possible, but it exists.



  • @Bulb said in Going to the Google's walled garden with Go:

    Maybe some of the logic is done at compile time rather than class load time, so it fails earlier, but that's about it.

    There are frameworks like that, for example https://micronaut.io



  • @dkf said in Going to the Google's walled garden with Go:

    There are several different definitions. I think a true metaclass is an entity which conceptually subclasses the class of classes, and so can customise the behaviour of new (at the stages prior to calling the constructor). Not all languages allow you to explicitly do that, but where you can do so you don't use anything like as many funky factories and stuff like that.

    The whole AbstractSingletonProxyFactoryBean meme is actually just about the lack of metaclasses (and metaprogramming in general) in Java, which must be band-aided by a heap of reflection (or worse, code generator) utilities. People who use it in a rant about "OOP" obviously have no idea what they talk about. Well, to be fair, they probably just have no idea what Spring core framework actually do (which is of course OK for non-Java developers, but not an excuse to talk out of one's ass).




  • Fake News

    @dkf said in Going to the Google's walled garden with Go:

    (Yes,. I could have used constructor injection in this case. I didn't.)

    Do those fields have a setter somewhere or are they injected through Spring Magic (i.e. reflection bypassing the private access modifier)?

    What if you wanted to manually construct this class to inject a dummy or mock? Seems like it's not o easy.



  • @Kamil-Podlesak said in Going to the Google's walled garden with Go:

    @dkf said in Going to the Google's walled garden with Go:

    There are several different definitions. I think a true metaclass is an entity which conceptually subclasses the class of classes, and so can customise the behaviour of new (at the stages prior to calling the constructor). Not all languages allow you to explicitly do that, but where you can do so you don't use anything like as many funky factories and stuff like that.

    The whole AbstractSingletonProxyFactoryBean meme is actually just about the lack of metaclasses (and metaprogramming in general) in Java, which must be band-aided by a heap of reflection (or worse, code generator) utilities. People who this in a rant about "OOP" obviously have no idea what they talk about. Well, to be fair, they probably just have no idea what Spring core framework actually do (which is of course OK for non-Java developers, but not an excuse to talk out of one's ass).

    Well, metaclasses are not exactly part of the original vision for OOP. Or of OOP at all, really.

    The initial idea with Java was that OOP can solve all the things and does not need metaprogramming. It turned out not to be the case.



  • @Bulb Metaclasses were a part of the original version of what then? Genuinely curious.



  • @stillwater My understanding is that they basically came out of the concept of “aspect-oriented programming”.



  • @Bulb DON’T. I’ve been bitten by it so much that’ll always be the thing I’ll never talk about to my therapist.


  • Discourse touched me in a no-no place

    @LaoC said in Going to the Google's walled garden with Go:

    That's still too restrictive. A metaclass' instances are classes, so it can be used to query and modify anything about an existing class, be it constructors, regular methods or attributes, or even create classes on the fly, at any time. Of course that's subject to the restrictions of your language runtime so all of it is rarely possible, but it exists.

    I'm used to a class system where I can change the class hierarchy and what an instance's class is at runtime. In that system, new is just a conventional method on the class of classes, and can be overridden to do any old thing you want. But it is also the only thing that actually knows how to synthesise an instance of anything so you take a bit of care. (It also allows defining entirely new concepts for classes in user code.)

    Yes, you can break classes that way easily. If you break them, you get to keep the pieces. Some people like broken toys.

    That particular language does not use classes as security boundaries at all.



  • @dkf Interesting. Can you tell me a use case where you’d actually do that? Or point me to resources where they talk about doing that?


  • Discourse touched me in a no-no place

    @JBert said in Going to the Google's walled garden with Go:

    Do those fields have a setter somewhere or are they injected through Spring Magic?

    I've used Spring Magic because I'm lazy. I could have written auto-generated setters if I cared to overcome the :kneeling_warthog:

    What if you wanted to manually construct this class to inject a dummy or mock?

    Then I might put setters in. Or I might tell the test to substitute mocks for the real beans and the wiring will just put them where they're needed. (I use a mock configuration, but that's done by overriding where the configuration file is for the test.) I try to avoid solving problems before I need to.



  • @Bulb said in Going to the Google's walled garden with Go:

    @Kamil-Podlesak said in Going to the Google's walled garden with Go:

    @dkf said in Going to the Google's walled garden with Go:

    There are several different definitions. I think a true metaclass is an entity which conceptually subclasses the class of classes, and so can customise the behaviour of new (at the stages prior to calling the constructor). Not all languages allow you to explicitly do that, but where you can do so you don't use anything like as many funky factories and stuff like that.

    The whole AbstractSingletonProxyFactoryBean meme is actually just about the lack of metaclasses (and metaprogramming in general) in Java, which must be band-aided by a heap of reflection (or worse, code generator) utilities. People who this in a rant about "OOP" obviously have no idea what they talk about. Well, to be fair, they probably just have no idea what Spring core framework actually do (which is of course OK for non-Java developers, but not an excuse to talk out of one's ass).

    Well, metaclasses are not exactly part of the original vision for OOP. Or of OOP at all, really.

    The initial idea with Java was that OOP can solve all the things and does not need metaprogramming. It turned out not to be the case.

    Eh, it depends. This is true for the Simula tradition and its popularity in 80s, but the other traditions definitely had metaprogramming in one way or another. Smalltalk defitely had metaclasses and Objective C basically too (although quite limited). And don't get me started about CLOS!

    Unfortunately, the big trinity of Object Pascal, C++ and Java cemented the Simula orthodoxy.


  • Discourse touched me in a no-no place

    @stillwater said in Going to the Google's walled garden with Go:

    Can you tell me a use case where you’d actually do that?

    Simplest possible use case for metaclasses is making something like an enforced Singleton. You say new and it just gives you the existing one instead.

    Use case for extending definitional capabilities is adding stuff like automatic getters and setters or other boilerplate generation.

    The use case I've heard of (from some guys based in Virginia) for mutation was someone doing simulations of disasters (flooding, fire, etc.) on warships. The mutation would happen when one of the crew changed role, perhaps because they'd acquired breathing equipment and were then allowed to tackle a fire, because then they'd change their behaviour pattern completely. It's all quite complicated stuff. Doing it without mutation would be possible, but would involve some horrible mess of pluggable Strategy instances or the "do my behavior" method from hell. Actor modelling is complicated, but that's because what it is modelling is complicated. Doing it without OO is even worse.

    Yet another use case for mutation is when you're deserializing an object. You need the object to exist to deserialize into it. (I'm not 100% sure how Java and C# actually solve this.)

    I'm not sure if there's a recording online of the conference where I presented some of this (not the simulation one, which totally wasn't my work). They were having technical problems with the video camera.



  • @dkf you presented this in a conference? I don’t know how to ask for it without having you to doxx yourself a little. Is there a YouTube link? I’ve learnt about so much stuff from badly made recordings I wouldn’t have learnt otherwise.


  • Discourse touched me in a no-no place

    @stillwater said in Going to the Google's walled garden with Go:

    Is there a YouTube link?

    I don't know. As I said, they were having technical problems with the camera. (I think the batteries were refusing to hold their charge, so only the first half of any session would get recorded.)


  • ♿ (Parody)

    @Mason_Wheeler said in Going to the Google's walled garden with Go:

    @dkf Oh wow. If people are using DI frameworks to do stuff like that now, it's getting even worse! 🤢

    Just in ways that you can't describe, or what?



  • @dkf said in Going to the Google's walled garden with Go:

    Yet another use case for mutation is when you're deserializing an object. You need the object to exist to deserialize into it. (I'm not 100% sure how Java and C# actually solve this.)

    At best, it's a use-case. Clearly Java and C# can manage without it. I'd guess that it's because they are able to create a blank instance without calling the normal constructor behind it, and then populate the fields based on the previously serialised data blob. A sort of "extrospection" (or maybe "interspection"), if you like (I don't, because it's a made-up word, but a reasonable one...), where an object on the outside (the deserialiser) is able to grope the data structures of the new object, in the same way that the object can analyse itself by introspection.

    Well, that's how I'd build it, anyway.

    ((Digression: C++ can't do any of this automatically, which some would frame as a weakness, but I'm not 100% convinced that it isn't a strength, because it makes sure that the programmer thinks(1) about what's going on.))

    (1) Heaven forbid that we hope that programmers think about what they are doing ...)



  • @Steve_The_Cynic said in Going to the Google's walled garden with Go:

    ((Digression: C++ can't do any of this automatically, which some would frame as a weakness, but I'm not 100% convinced that it isn't a strength, because it makes sure that the programmer thinks(1) about what's going on.))

    C++ reflection working group (some SD-somenumber) has been working on that for a while. Wish they'd get going a bit faster. (There's an experimental feature spec out, though.)


  • Discourse touched me in a no-no place

    @Steve_The_Cynic said in Going to the Google's walled garden with Go:

    I'd guess that it's because they are able to create a blank instance without calling the normal constructor behind it

    I had a look at what Java does. It's intensely magical, and might actually be synthesising constructors for classes without entering them into the classes' vtable in some cases.



  • @dkf said in Going to the Google's walled garden with Go:

    (I'm not 100% sure how Java and C# actually solve this.)

    In Java, the serialization machinery calls the no-arg constructor of the deepest non-serializable class, magically makes the object become the correct class, then does magical injection of all the field values. I don't think you can do this magic outside of that machinery.

    In C#, you call Activator.CreateInstance to make an instance of a class, skipping all construction and initialization. The serialization machinery then either hands off to a special method in the class to pull fields out of the serialized stream or just sets whatever public properties it got.


  • BINNED

    @dkf said in Going to the Google's walled garden with Go:

    @Steve_The_Cynic said in Going to the Google's walled garden with Go:

    I'd guess that it's because they are able to create a blank instance without calling the normal constructor behind it

    I had a look at what Java does. It's intensely magical, and might actually be synthesising constructors for classes without entering them into the classes' vtable in some cases.

    Why would a constructor be in the vtable? 😕



  • @stillwater said in Going to the Google's walled garden with Go:

    It’s like one gets paid for needlessly adding complexity where none exists. Oh wait…

    Exactly like my cow-orker Jim. He needs a StateMachine (at least, he calls that thingy a StateMachine, but that might be his misconception, because he tells it to switch to a state given as a parameter, and then the StateMachine sets some global variable to that value...) when he has to call a few functions one after the other, in always exactly the same order...



  • @JBert said in Going to the Google's walled garden with Go:

    What if you wanted to manually construct this class to inject a dummy or mock? Seems like it's not o easy.

    In C#, I usually add an internal static MyClass CreateForTesting(some parameters) method. With the internal modifier (and an InternalsVisibleTo in the properties of the assembly) plus the ForTesting part of the name, it should be clear that it is meant for testing purposes only. But I have some cow-orkers.



  • @TwelveBaud said in Going to the Google's walled garden with Go:

    In C#, you call Activator.CreateInstance to make an instance of a class

    Nota bene: you have to write a public constructor without parameters in order to do so (well, it is created by default if you do not add any other constructor, but when you use injection via the constructor...).


  • Considered Harmful

    @dkf said in Going to the Google's walled garden with Go:

    @Steve_The_Cynic said in Going to the Google's walled garden with Go:

    I'd guess that it's because they are able to create a blank instance without calling the normal constructor behind it

    I had a look at what Java does. It's intensely magical, and might actually be synthesising constructors for classes without entering them into the classes' vtable in some cases.

    Since no sane person uses it, this is fine, keeps them busy and away from the GC.


  • Discourse touched me in a no-no place

    @topspin said in Going to the Google's walled garden with Go:

    Why would a constructor be in the vtable?

    Why would it not be? It's gotta be kept somewhere normally and having them in there makes it easier to build a reflection system.


  • Discourse touched me in a no-no place

    @BernieTheBernie said in Going to the Google's walled garden with Go:

    @JBert said in Going to the Google's walled garden with Go:

    What if you wanted to manually construct this class to inject a dummy or mock? Seems like it's not o easy.

    In C#, I usually add an internal static MyClass CreateForTesting(some parameters) method. With the internal modifier (and an InternalsVisibleTo in the properties of the assembly) plus the ForTesting part of the name, it should be clear that it is meant for testing purposes only. But I have some cow-orkers.

    I have an evil bit of code that looks up the stack and checks for a method that is implementing a test case, throwing if there isn't one. I call that from the API places that are marked as for testing only, so that anyone trying to sneak in gets slapped down. (The testing API that you can get that way allows injecting mocks, sidestepping transaction management, disabling invariant checking, and other fun things like that.)

    But that's really just for testing, and is actually cleaner than what I was doing before as I can make all the places where it is poking into be hard locked down otherwise. And it isn't meant to defend against an adversarial attacker but rather an idiot like me.


Log in to reply