big OOP

so is object oriented programming like scientology for people who don’t want to get out of the house or what

3 Likes

imo it’s more like liberalism, a deeply flawed system that took over the world because it had some apparent advantages over what came before but which can produce all of the same negative outcomes when approached with the right spirit of ingenuity. this would make smalltalk the french revolution and java napoleon. also functional reactive programming would be communism and react would be stalin. but that falls apart if you assume the graphical user interface is capital accumulation. or does it. i’m lost

11 Likes

Jesus Christ you two

2 Likes

I love OOP so I’m going to stand up for it even if it makes me look like a doofus and it’s only kind of relevant in this thread. Somebody’s just gotta after comments like these. I don’t know why everyone wants to rag on it all the time…although I do suspect Java is somewhat to blame.

Of course, part of what I think makes conversations like these challenging is that the phrase “object-oriented programming” means something a little (or a lot) different to everyone. I don’t think it helps that “functional programming” is the same way. Sometimes I think we should throw out all of this programming paradigm jargon and start over with more precise terms.

But, anyway, I think one thing that ties together most language features that get called “object-oriented,” at the very least, is that they allow you a wide syntactic design space when you’re creating an interface to some set of data. That’s really what I like about them more than anything. An object gives you a “syntax scope,” where you can pretty freely choose what the messages to the object mean character-by-character without affecting the rest of the codebase. I think people often don’t focus enough on syntax when they’re considering OOP—when it’s done well it gives you a lot to lean on in pursuit of “representing your ideas directly in the code,” that vaunted ideal.

To give an example of what I mean, say we have a SQLite database table storing 4D vectors:

sqlite> SELECT * FROM vectors;
id  x      y      z      w   
--  -----  -----  -----  ----
0   58.93  21.75  97.94  0.24
1   79.09  49.08  17.31  0.49
2   88.41  17.85  16.24  0.37

Let’s say we want to get the X, Y, and Z components of each vector divided by their Ws. I think we can all agree that, for a given vector v, this would be a nice way to express the operation we want to perform in code:

v.xyz / v.w

SQLite’s syntax doesn’t really allow you anything quite that elegant:

sqlite> SELECT x / w, y / w, z / w FROM vectors;
x / w             y / w             z / w           
----------------  ----------------  ----------------
245.541666666667  90.625            408.083333333333
161.408163265306  100.163265306122  35.3265306122449
238.945945945946  48.2432432432432  43.8918918918919

This is okay, and it makes sense if you’re familiar with SQL, but I don’t think it’s the first thing you would come up with, exactly—it makes certain concessions to the “databaseness” of the environment.

Now, let’s say we’ve loaded this data into four-component Ruby Vectors, and we’ve extended Vector with my Ruby swizzling mixin that I posted the other week, in this manner:

class Vector
  include Swizzleable

  def swizz_vals
    (0...[4, size].max).map { self[_1] }
  end

  # if there's only one component, return it as a scalar; otherwise, return a
  # Vector
  def post_swizz(comps)
    if comps.size == 1
      comps[0]
    else
      self.class[*comps]
    end
  end
end

Now if we have all the Vectors in an enumerable called vecs, we can do:

vecs.map {|v| v.xyz / v.w }
#=> Vector[245.54166666666669, 90.625, 408.0833333333333]
#   Vector[161.40816326530614, 100.16326530612244, 35.326530612244895]
#   Vector[238.94594594594594, 48.24324324324325, 43.89189189189189]

To me, this is a great example of the power of OO (with a bit of help from dynamic typing). You probably wouldn’t want to define functions at the top level with names like w or x, but in the interface of a particular type, the meanings of strings like that have a much more constrained range of possibilities generally speaking, so having methods/member functions with those kinds of names can be obvious and unproblematic. Also, mature OO languages often give you a wide variety of ways to express how you want the interface to a given type to work, even very abstractly (Swizzleable uses Ruby’s metaprogramming features to express what it wants in only a short bit of code, calling things on the including class like define_method and public_method_defined? at include time).

In a simple procedural language like C, we would probably be stuck with a syntax like

vec_scalar_div(vec_swizz(v, vec_xyz), vec_scalar(v, vec_w))

or something, and we would probably end up generating the definition of the vec_ swizzling enum as part of the build process instead of being able to express it abstractly in the language itself. Even in a powerful functional language like Haskell, although we could define the mapping between “swizzling strings” like xyz and actual component accesses abstractly in our code, I think we would probably still end up with code like

swizz v "xyz" / comp v "w"

because of the namespacing issues that would otherwise arise from defining functions with names like w at the top level (the Haskell community seems to have spent years and years trying to come up with a nice solution to this issue and although there have been a variety of proposed extensions and things, I don’t think anything has caught on to the point of being truly mainstream, although a more seasoned Haskeller is welcome to correct me if I’m wrong). Of course, even if we did define define functions like that at the top level, we would end up with

xyz v / w v

which is at least terse, but I don’t think it’s quite as obvious as the v.xyz-style syntax (even mathematicians would use subscripts, after all).

Also, I think it’s worth noting that OO languages generally make it easy to share “bits of an interface” between different types, however it makes sense to do that. Swizzleable, as a mixin, is a nice example of this; you can use it to add swizzling to the interface of any type for which you think it makes sense (Ruby supports mixins via modules, C++ does it with abstract classes, Java/C# with interfaces, etc.). In the more narrow cases where you want to provide an implementation along with the interface, you can do that too, via what people generally mean by “inheritance” in OO. This gives you a nice, fine-grained way to make things convenient for the users of your “bit of an interface,” depending on whether it would be nicer for them to design the implementation for it or use a ready-made one (or both! languages like Ruby and C++ let you pick between things like this conditionally, C++ at compile time via templates). Languages without OO, and even languages with OO but without “classical” OO, don’t necessarily give you so many options for composing interfaces piecemeal like this; Haskell’s type classes give you something kind of similar at least, but even then you don’t have as much flexibility for the syntax of the resulting interface.

Zooming out a bit, I think the basic “message passing” idea popularized by Smalltalk is a nicely intuitive way to think about information moving around in a computer program, especially one with complex patterns of interaction between its different parts. It’s easy for us to understand the idea that both a kangaroo and a CPU might understand the message “jump,” but in different ways. Also, once we’ve established that we have a kangaroo and a CPU responding to messages, it’s a hop, skip, and jump to the idea of having a conductor in the program waving their baton around and sending the message “hop” to the kangaroo and “jump” to the CPU at different times and so on. I think it’s nice to remember that SIMULA, the language which is often considered to have founded OOP, was designed for writing simulation programs, and a computer game is basically a kind of simulation program.

3 Likes

tbh i was mostly being glib bc i hated how the godot documentation kept trying to encourage me to “learn to think like a programmer” (something i don’t want to do!) and how trying to maintain fidelity to the monist everything-is-a-nested-node concept meant the english syntax explanations became kind of tortured (like carefully not distinguishing a level and an object within it since they’re both “scenes” - which i get but find unhelpful). in general “seems confusing but is actually more elegant” is a warning sign for me when i’m trying to do things… this is the same way we ended up with the doctrine of the holy trinity… when will humanity learn

anyway griping aside godot is pretty fun so far and i do enjoy i can just leave it running in the background without it slowing everything else i’m doing. going to doggedly try learning the 3d parts of it soon and then i guess try learning TRENCHBOOM the level editor for making things go boom in trenches

5 Likes

I believe thecatamites is railing against one of the standard recommendations for sharing script implementation between nodes. it’s the dreaded monster of OOP, Grendel lurching from the bog clutching a sword inscribed “I N H E R I T A N C E”

I usually start a dive into OOP by showing what a vtable is and then walk through https://www.piumarta.com/pepsi/objmodel.pdf-. that either explains it or kills all curiosity

1 Like

friends ily and i am partially the target audience for this kind of discussion but i feel like oop discoursing is sooo not the point of this thread

3 Likes

In all fairness I’ve personally felt like Godot’s whole design is really questionable and not very humanistic, at least in the time I’ve spent with it so far. In terms of all the stuff I was talking about above, like how OO features in languages generally let you compose interfaces in an expressive and piecemeal manner where you can define little “bits of an interface” and then put them together type-by-type in sophisticated ways to make the nicest-possible interfaces in each case, etc. etc., I feel like Godot’s awkward “one script per node” design, coupled with the fact that GDScript has only single inheritance via extends and no equivalent to Ruby’s include or a C++ pure abstract class or anything, is sort of the opposite of this: it greatly constrains your ability to define each interface, and instead forces you through weird, needlessly-laborious workarounds where you define a bunch of little separate interfaces with hard-to-track dependencies. The thing that’s really remarkable to me is that the documentation excerpt you cited describes much of this directly, which indicates to me that the developers have thought of these issues, and…just…idk. It could be so much nicer. I have a lot of similar complaints about Unity’s design I guess—I think the whole “scene tree” idea sounds better on paper than it ends up being in practice, at least as an approach to organizing code (it works okay where local coordinates are concerned, I think, but maybe it should just stick to that only).

I don’t want to just be a Negative Nancy about Godot though either, I’m really glad that it exists in any case…

The other day I was actually reading arguments between Oneness Pentecostals and more vanilla(?) Protestants about the Trinity, and I did keep seeing arguments like, “It might sound confusing, but actually the Trinity is the most beautiful way of making sense of scripture,” which I thought was kind of funny since it sounded to me like an immediate contradiction, speaking as someone with like, no background in Christianity really.

Anyway that is to say, I totally agree with your statement!! In particular I shudder at all the harm wrought in history by people who want to impose an over-“elegant” system on something in lived experience that is just too rich and complex to be amenable to it. It makes me think of ol’ Boethius in his Principles of Music, where he asserts again and again that the senses are not to be fully trusted in musical matters, but rather should be subservient to reason, e.g.:

The sense perceives a thing as indistinct, yet approximate to that which it is; reason exercises judgment concerning the whole and searches out ultimate differences. So the sense discovers something confused, yet close to the truth, but it receives the whole through reason. Reason itself comes to know the whole, even though it receives an indistinct and approximate likeness of truth. For sense brings nothing whole to itself, but arrives only at an approximation. Reason makes the judgment.

Based on this he asserts that small whole-number ratios give the ultimate in consonace:

Judgment should be exercised with respect to all these consonances which we have discussed; one ought to decide by the reason, as well as by the ear, which of them is the more pleasing. For as the ear is affected by sound or the eye by a visible form, in the same way the judgment of the mind is affected by numbers or continuous quantity.

Given a line or a number, nothing is easier to contemplate, with either the eye or the intellect, than its double. After this judgment concerning the double follows that of the half; after that of the half, that of the triple; after that of the triple, that of the third. Thus, since it is easier to represent the double, Nicomachus considers the diapason to be the optimum con­sonance; after this the diapente, which contains the half; then the diapason­ plus-diapente, which contains the triple. The others he ranks according to the same method and plan.

And yet! (from Tuning, Timbre, Spectrum, Scale by William Sethares)

…most people prefer their octaves somewhat stretched, even (or especially) when listening to pure tones. A typical experiment asks subjects to set an adjustable tone to an octave above a reference tone. Almost without exception, people set the interval between the sinusoids greater than a 2/1 octave. This craving for stretching (as Sundberg notes) has been observed for both melodic intervals and simultaneously presented tones. Although the preferred amount of stretching depends on the frequency (and other variables), the average for vibrato-free octaves is about 15 cents. Some have argued that this preference for stretched intervals may carry over into musical situations. Ward notes that on average, singers and string players perform the upper notes of the major third and the major sixth with sharp intonation.

So, in music as in programming, I think it’s best to trust your visceral experience first, and use your reason to produce the nicest-feeling things on that basis.

Is it really inheritance, though? The weird, tortuous multi-node constructions Godot wants you to use to get shared behavior there sound more to me like “zealous over-composition,” of the sort I associate with a kind of fear of inheritance in a way. At least as far as I understand things in Godot at this point, if you put a script on a node with children, it’s not that its children automatically inherit its behavior, it just means you can fish them out of the tree relatively conveniently and say things to them and so on, which I would call composition. Maybe I misunderstand or am underinformed though, or maybe the whole set of terminology we’ve been given is just too vague and we’re talking about different things in practice. (I know that you can have single inheritance between scripts/nodes with extends and between scenes with New Inherited Scene etc., but those docs as quoted don’t seem to mention that for whatever reason.)

I guess, my instinct right now to suggest what OOP is about to someone uninitiated is to say that it allows you to write code along these lines:

kanga = Kangaroo.new
dog   = Dog.new(:lazy)
kanga.jump dog 
#=> "The kangaroo jumps over the lazy dog"

cpu = CPU.new(64)
cpu.jump 0xdb5f953729dc83fe
#=> "rip: 0xdb5f953729dc840d"

To some extent I think like, the details of how you implement things like this in the language machinery are a little aside from what makes it nice to use or how to look at it in the way that allows you to have the most fun with it or something like that. I have started out talking about vtables before, but it was in conversation with an electrical engineer who knew only C and various assembly languages, and I thought maybe if I talked about how to build up OO in C it would help make things more clear for them. Sadly though, even though they got what I was getting at, by the end of the conversation they were still skeptical about it being very useful, so even there I think I maybe should have started with the pleasant kind of syntax it affords, and even how it can give you a lot of immediate and fine-grained control over syntax.

By the way apropos of nothing I’m sorry I haven’t said anything about your sweet comments on my rennet shader!! I thought it was so nice of you to try porting it to another shading language and so on. I spent a while looking at your code but I felt like I wanted to go over it more before responding, and got pulled away before I was satisfied and still haven’t gone back, so I’m sorry for that. I have an updated version of the shader I need to post that supports textures with transparency, so when I do that I’ll give your posts their due as well.

1 Like

God sorry I was working on that post for hours and didn’t see your reply. Sorry if I’m driving you nuts. At least the OOP discussion was only part of my post…I do think it has some relevance also in terms of like, how to use Godot in the nicest possible manner and that sort of thing.

just split it out imo

@moderators if you’d be so kind to alley this oop

edit tysm

2 Likes

ruby is the most pleasant language i’ve worked in by far. the microculture i was a part of when i was writing a lot of ruby would make a distinction between more procedural ruby, with lots of long methods and mutations of object state, generally a lot of bad decisions in terms of factoring that impaired the code’s readability and maintainability. this was contrasted with a more functional-ish style where most of an object’s methods were pure functions and state tracking was kept to the absolute minimum, it made everything much easier to reason about. so there was this dichotomy between “object-oriented” code meaning “as imperative as the writer could get away with, but more confusing because control flow jumps between hopelessly enmeshed objects” and “functional” code meaning “as close to pure functional programming as the writer could get away with, with an absolute minimum of state tracking” - it’s an unfair characterization for sure, since ruby being an object oriented language actually gives you the flexibility to do things either way. it’s perhaps equally unfair to consider sharecropping the quintessence of liberalism, these attitudes come from a place of frustration

1 Like

nooooo don’t use inheritance, that’s a form of nonlocal goto, just graft on some clearly delineated service objects for god’s sake

ok, maybe a little, as a treat for basic entities in the domain you’re modelling but the last thing anyone needs is a big tree of inheritance relations

guilty. i have seen inheritance go to some nasty places

I don’t think that’s a well-supported option in Godot, and script reuse with extends is. so that’s what people recommend

I’m at a stage in my career where the constraints of the domain are easier to accept, like the difficulty of maintaining cache locality in a high-level language

1 Like

I share your frustrations about that kind of code—like, deeply confusing huge lattices of interdependent objects where it’s hard to track down the origin of any given piece of behavior and so on. I would say that imperative OO Ruby code (or code like that in other languages) can be really easy to both reason about and change without surprising regressions, but it can be challenging to figure out how to structure a large codebase that way, and I think in a group it really helps to have close collaboration between the developers to achieve it (like, via pair programming and stuff, or at least a lot of detailed regular discussion) so that everyone has a good sense of the evolving big picture and so on. Comprehensive and fine-grained automated testing can also really help, as can people being disciplined about documentation etc. (Obviously not every commercial environment has a culture like this.) Because Ruby is a language with few safeguards and tremendous freedom for the programmer, I think it’s easy to make an unworkable codebase with it, but it’s also possible to have an especially excellent, well-behaved codebase when conditions allow.

As far as writing pure functions goes, in some ways I have the instinct to say that you can still produce daunting spaghetti that way (I’ve seen some deeply confusing Haskell codebases with long sprawling opaquely-named functions and so on…in some ways I think you have to be careful if you want clear, easy-to-work-with code regardless of the language or design). It does seem to me like there’s something about pure functions though that helps people code-design-wise. I think maybe it’s just that it’s obvious what the surrounding context is when the function runs, whereas with mutable state it can be a huge struggle to know that comprehensively—at least if the codebase hasn’t ended up being very pleasant.

Actually, speaking of, I think for me one of the most helpful mental tools I’ve picked up for writing easy-to-work-with imperative classes, in basically any language with classical OO, is to figure out the invariants the object expects, document them, ensure that they’re established during the object’s initialization, and then be careful to maintain and/or check them as necessary in each of the object’s methods. If you’re writing tests for the class, the invariants let you know what sort of test harness you need, and trying to keep the invariants lightweight helps you write classes that aren’t too tightly-coupled. I also think, in a dynamic language like Ruby, duck typing, and in general focusing on the interface over the concrete type (e.g. using public_method_defined? over class == and that sort of thing), helps a lot to keep tight coupling to a minimum. (Something I think is kind of unfortuante about static typing is that the path of least resistance is generally to depend on an entire other concrete type; you have to go out of your way to define a pure abstract class or interface or w/e to break that dependency, and even then, you still have to consume the entire interface defined therein. In a dynamic language you can easily specify only the parts of an interface you actually need to depend on case-by-case, which can make the whole codebase more resilient and flexible.)

Is inheritance really a form of nonlocal goto any more than any other procedure call? I guess like, I see what you’re saying in sort of qualitative terms though I think—like, I guess, the classic objection that it breaks encapsulation and so on.

I’m tempted to say that I really like inheritance, but I think it kind of depends on what sort of inheritance. I think like, I find way more use for interface inheritance, as you get with a C++ pure abstract class or by includeing a Ruby module or that sort of thing, than I do for implementation inheritance, as you get from inheriting from a C++ base class with data members or by using < in Ruby. C++ affords you amazing code design possibilities by combining interface inheritance and templates—sometimes you have to think really hard :stuck_out_tongue: but you can come up with abstract class lattices that are both very technically satisfying (performant, type-safe, etc.) and require the minimum of work from implementors of derived classes. In Ruby, the metaprogramming possibilities afforded to you at module include time allow you to basically rewrite the entire including class based on whatever information is available in the program at that moment during runtime, so it’s kind of like C++ except that you can go even further (although of course you have less say about what happens close to the metal in the process). You can write libraries that will do lots of the coding for you this way.

I think implementation inheritance is a lot more dangerous and kind of dubious, and I tend to use it more as a quick crutch than as something I leave in a codebase long-term, I think. As a general rule, I feel like it’s more useful and flexible in the long run to have an interface that can work with a variety of underlying implementations, and just leave the inheriting class to tell the interface how it satisfies its requirements, rather than have the base class force an implementation on the derived one. However, I do think there are cases where it’s nice to have a “default implementation” available in the base class, in situations where there’s an obvious default, to save implementors of derived classes from having to write boilerplate code. In a language like C++, this has to be clearly documented I think because it can impact the representation of the derived class in memory, and I think in some ways that speaks directly to its dubiousness (because you might rather not document an implementation regardless of what it is). The benefits can outweigh the downsides sometimes but you have to be careful I would say.

The major downside I can think of to composition is that if you want to expose part or all of the interface of a wrapped object in the interface of a wrapper object, there’s not always a straightforward way to achieve the delegation without having to duplicate parts of the wrapped interface or at least write and maintain some kind of boilerplate code. There are a lot of situations where you basically want some kind of standard data structure with a bit of specialized functionality added on, and it can be convenient in those cases to be able to tell the world that that’s what it’s holding onto without having to actually expose the wrapped data structure object directly. Inheritance can provide a straightforward way to get that. (Of course, if you can inherit the appropriate interface for the data structure, you can often just do a tiny bit of delegation to the wrapped type to satisfy the requirements of the interface and then get the best of both worlds.)

You can do this okay in Godot—at least as I understand them a service object isn’t really anything special necessarily. Like, let’s say you want to have something that can hold onto a Color and generate other Colors by randomly shuffling the components of the Color it’s holding. It’ll keep track of the last color it generated so that it doesn’t do the same one in a row (there are arguably more sophisticated ways of doing this but just for the sake of a simple example)

extends RefCounted
class_name ColorShuffler

var source_col = null
var last_col   = null

func _init(src_col):
	source_col = src_col
	last_col   = source_col

func gen_col():
	var col = Color()
	var i = 0
	var ns = [0, 1, 2]
	ns.shuffle()
	for n in ns:
		col[i] = source_col[n]
		i += 1
	col.a = source_col.a
	return col

func color():
	var col = last_col
	while col == last_col:
		col = gen_col()
	last_col = col
	return col

Having ColorShuffler inherit from RefCounted ensures that it will get garbage collected without having to incur the overhead of Node.

Then, if you have a MeshInstance3D with the StandardMaterial3D on it, we can give it this other script to change its color every so often, making use of a ColorShuffler:

extends MeshInstance3D

@export var period = 1.92

var elapsed = 0
var shuffler = null
var mat = get_active_material(0)

func _ready():
	shuffler = ColorShuffler.new(mat.albedo_color)

func _process(delta):
	elapsed += delta
	if elapsed > period:
		elapsed = fmod(elapsed, period)
		mat.albedo_color = shuffler.color()
1 Like

thanks for the example, that’s exactly what i mean by service objects, and i’m glad that gdscript is a normal enough language that the pattern is possible there. one of the problems you see in the sorts of applications i’ve worked on if people piling a bunch of extraneous logic into the classes required by the application framework, so you get a lot of incidental details about, for example, sniffing out the file types of uploads or interacting with a particular third party service put into what is essentially an html template or a definition of a background job. if you pull the convoluted and detailed logic of a computation into its own module with clearly defined boundaries, you can understand and test it in isolation, which puts you in a much better ergonomic situation - this actually holds for programming in any paradigm (and i definitely agree that functional code can be poorly factored and pointlessly convoluted). “service objects” are just a local application of the pattern to object-oriented framework-based applications - i haven’t built anything in godot so i am guessing about what “1 script per node” plus only single inheritance actually means in context, but it sounds like a situation that would tempt someone to cram way too much into those object definitions, which service objects could help with

2 Likes

yeah i was speaking very imprecisely here, what i’ve actually been finding annoying about working on an object-oriented codebase with multiple inheritance lately is seeing references to methods or properties that aren’t defined in the current file, then having to manually open all of the classes that are mixed into this one until it turns up. i’ve also seen subtle bugs introduced where code from parent classes referred to properties of this that weren’t defined on all of their child classes. and in this situation the inheritance system is built into the web framework, so you need much more scaffolding to put unit tests around them, like a fake browser environment that runs in nodejs, and you are only testing their behavior after they’re combined with other classes to form a giant blob. there are ways of working around all of this, it just strikes me as untidy

1 Like

Sure ^^ and god, yeah, me too…at least if you have that you know you can break away from the engine.

Yeah, sometimes I question a little the whole wisdom of frameworks-as-such on that basis…not that I think they’re fundamentally bad or anything, just in a kind of psychological sense, like the mindset they tend to put people in like you’re saying…like, how the whole idea of “framework” can easily come across as like “a series of containers to put all your code in” instead of “another layer.” When the framework is a set of classes you inherit from, and the language has single inheritance, I think it tends to exaggerate this psychological effect, which is maybe kinda sad since that’s how most frameworks I’ve used work (in both web and game development). I remember years ago watching a video of Sandi Metz doing a kind of Q&A at Hashrocket where she said offhand like, “Try to avoid frameworks that obligate you to inherit from their classes,” and someone was like, “What about Rails?” and she just kind of smiled mischeviously :rofl: I think of that as a “big issue” in the Rails world—people talking about POROs and “going beyond Skinny Controller, Fat Model” etc. I haven’t done Rails full-time for years now but glancing around online some it seems like people still talk about that, which makes sense to me—it’s like somthing people would have to continuously pass down to newcomers. It’s nice to think that a framework which provided a set of mixins instead would lead to something better, but I’m not sure if it would in practice—it might just lead to chaos in a lot of codebases :sweat_smile: I kind of feel like this might be a sociocultural issue, ultimately, that can’t really be solved through API design alone…maybe it’s because people aren’t really taught to code at first in a way that encourages independent critical thinking a lot of the time, so they have to kind of learn that as they go.

It’s too bad you don’t have tooling to help more with that, it sounds really frustrating! That is one thing I think of as an advantage that statically-typed, compiled languages have (especially popular ones :stuck_out_tongue:)—like how it’s easier for people to develop tooling to like, jump from the occurrence of a symbol to its definition in your editor, move your cursor over a symbol in the editor and have the documentation for it appear in another pane, generate documentation that includes interactive UML diagrams showing the class relationships, etc. etc. When I started working with C++, I was really amazed by how strong the tooling can be in those kinds of regards compared to a language like Ruby; with Ruby it feels so much more like it’s just “you, your editor, and the shell” (well, except for its awesome REPLs—the interactive debugging experience in Ruby is pretty luxurious I feel like :stuck_out_tongue:).

That does sound extremely annoying…sounds like the kind of situation where you would really want to keep the framework at arm’s length, yeah. Can I ask which it is? :sweat_smile: Now I’m curious. :stuck_out_tongue:

that is definitely what i’ve missed the most as ruby has lost market share and i’ve had to work in javascript more. i loved being able to write code as one-liners in a breakpoint and then copy/paste it back into the editor once the results it was giving me looked good, plus being able to display the source code of anything within scope on command - the exact opposite experience of having to debug the behavior of code running in a webview-based app on a mobile device. i wish repl-based editing was the dominant mode, it’s such a nicer experience

related: i remember being impressed by this video when it came out https://www.youtube.com/watch?v=72y2EC5fkcE

i’m generally in favor of static typing, and i’m reasonably certain that moving this codebase i inherited to typescript would allow the editor to solve the “where is this method from” problem, but it’s hard to budget the time for that when i’m the only person working on the project and there are active users with bug reports. maybe i’ll get around to it eventually

it’s vuejs, which i think of as a more down-to-earth version of react. the framework itself is totally fine, but it being less restrictive than react does seem to allow people to indulge some bad habits they might have learned writing php or jquery code or whatever

1 Like

I’m kind of worried that maybe I’ve been scaring some people away from this thread at least in some part :eek: I know programming can be a fraught and kind of hard-edged topic sometimes but I want this conversation to have a fun and welcoming atmosphere instead if it can…if you want to participate but feel intimidated or you have been participating and feel driven away, and there’s anything I could be doing differently that would help you feel more comfortable, please let me know. :raccoon: :hedgehog:

Yeah, I think out of all the languages I’ve used, Ruby has the debugging experience I probably like the most. (I do really appreciate that in GDB you can step through your code instruction-by-instruction and watch the CPU registers, not only for its use in debugging but also for optimization, but Ruby isn’t really about that kind of programming anyway. :stuck_out_tongue:) Speaking of REPL-based editing, you might already be aware but just in case, Pry’s edit command lets you open up your editor mid-session and edit-then-run the Pry input buffer (just edit), a source file (edit [FILENAME].rb [-l [LINENO]], edit CLASS[#METHOD], edit -m for the current method execution is within), or a tempfile (edit -t), and you can add the -p flag if you want to just do a monkey-patch for the current run instead of actually changing the source code on disc. I think all together it’s like, one of my favorite features in any debugger probably.

I’ve never done mobile development but that does sound like it could be clunky. I read the WebView docs a bit…I guess what you’re working on is sort of like, a “mobile-targeted SPA” but presented as a regular phone app? I suppose a lot of mobile applications must work like that in practice? A lot of them kind of feel that way to use, I guess. I can imagine how something like that would be a challenge to debug.

Yeah that is quite impressive!! Given that I can’t help but say that it would be nice if they would share the source code for their dev tools. :sweat_smile: Although, on that basis, I do think it’s worth noting, just for anyone that might feel envious, that GDB and Valgrind can get you really close to this—GDB can even do the execution rewinding and stuff (although not with that fancy GUI they show of course). They definitely get some special sauce in places out of the tight integration of everything with their engine, but like, you can at least get really close. If anyone is interested in debugging and optimizing C++ (or C) applications I can describe in more detail. :stuck_out_tongue:

Yeah isn’t that always the way…maybe someday time will stop for a while and then we can all take care of everything like this…

I guess, I kind of feel like I like both dynamic and static typing, but in a lot of ways it depends on the langauge for me in both cases. I haven’t tried TypeScript to be honest, I usually just write regular JS when I need to, but I’ve never found JS’s type system to be especially supple and accomodating I guess and I’ve definitely been bitten by its obscure type coercion rules before, so maybe I would prefer TypeScript if I did try it. I guess it sounds like you prefer it to plain JS? When I heard about it, I remember having the thought that if one was going to add static typing to the language the approach of PureScript might be nice, and I’ve spent a fair amount of time playing around with it because of that, but around the time the ES6 features started getting wide browser support I started just writing plain JS since it seemed usable enough. This is just in the context of my own projects though—I would probably have a different perspective if I was still doing web development for a living I would guess.

1 Like

holy shit this is black magic

1 Like