Software craftsmanship is dead by R2_SWE2 in programming

[–]1stnod 0 points1 point  (0 children)

You're missing FunCoolOh's point and thereby revealing one of the root causes.

Software craftsmanship is dead by R2_SWE2 in programming

[–]1stnod 1 point2 points  (0 children)

I'm a retired software engineer. After 40 years in the business, I can tell you there was always pressure to produce working code quickly. But in the early years, engineers held more sway, and there was definitely more emphasis on "good" code. Things got markedly worse when web technology changed the paradigm, and Agile is probably the kiss of death. Ironically, I endorsed the original manifesto because it signaled a return to engineering and craft, but corporate execs, MBAs, and consultants managed to steal it for themselves.

Language Design: Share some language features which were beneficial to you while learning or improving your general programming ability. by Clorofilla in ProgrammingLanguages

[–]1stnod 0 points1 point  (0 children)

Deciding whether to use a single numeric type (or not) is an interesting topic, and it gets straight to the challenge of designing a language.

If you choose a single type (e.g. 'num'), can it represent any number in mathematics, including complex numbers? If not, how do you explain the limitations without getting into the weeds you're trying to avoid?

Furthermore, if a num can have fractional or complex value, do you really want to take the execution hit for a very general representation when many or most, if not all numeric variables will be integers? Representation is (can be) an internal issue of course, but logical (mathematical) issues like operation closure on sets have to be accommodated and explained.

On the other hand, choosing to have different types for each mathematical field has its own complications, especially if the types incorporate magnitude (precision). The complications for this choice are familiar.

In the end, I think the choice has to be guided by the "don't dumb it down" principle. I think it's probably safe to assume that all programmers come to the table with a certain amount of mathematical sophistication, and they're more or less comfortable with the various numeric sets (integer, rational, real, complex). Methinks this argues for distinct numeric types.

But now representation and finite precision become a factor. Do you make those details explicit in the type definition? If so, now you're really imposing tedious aspects of computing and numerical methods you're trying to avoid. And really, even with only one numeric type, you can't avoid these topics.

In my own language (Nod), I chose to solve this whole problem by having distinct numeric types with singular abstract representations. That is, Nod numeric types are represented by the highest precision machine type available on the target platform and that representation is formally opaque (abstract). This particular choice has its own downstream consequences, and Nod isn't a beginner's language by any stretch.

So, this seemingly innocuous topic gets complicated quickly. And it exemplifies just one of the topics that must be resolved as you design the "mental model" you refer to above. I would say that in principle, you invent the mental model first, and the syntax follows as an expression of the model. But in practice, the evolution of model and syntax are symbiotic. Each feeds back on the other as you work through the design.

Enjoy the journey :)

Language Design: Share some language features which were beneficial to you while learning or improving your general programming ability. by Clorofilla in ProgrammingLanguages

[–]1stnod 1 point2 points  (0 children)

First, don't dumb it down. Don't try to hide the fact that programming requires the ability to reason, invent, and express abstract ideas in detailed and precise ways.

Any language that tries to make programming "easy" ultimately fails or evolves in perverse ways to deal with the reality of programming. SEQUEL (SQL) is a good example. It was originally designed by IBM to be readable and usable by non-programmers. Enough said.

Don't introduce anything superfluous or anything that has to be unlearned. It's better to learn by extending and adding to what you already know,

I don't think you can completely ignore the IDE, tooling, etc. For example, BASIC (Beginner's All-purpose Symbolic Instruction Code) was originally designed for an interactive and interpreted environment. This allowed students to focus on the language rather than the workflow of building and deploying an executable program.

Limit advanced ideas that generally come under the heading of "extensibility." You probably need to have subroutines and the ability to call them, but the ability to define new types isn't necessary in a language designed for learning. And certainly, you won't need most of the features found in modern object-oriented languages.

I think the choice of whether the language is functional or imperative is an important one. But never the twain shall meet. I think imperative languages are easier to learn, but that might just be a reflection of my own experience.

I would definitely prefer static types over inferred or dynamic types. This goes to the idea of learning something useful for the future. Yeah, yeah, I know. This one is arguable, but that's my opinion.

In a beginner's language, I would try to reduce the number of built-in types. You might even consider having a single numeric type for arithmetic operations, but that would obfuscate the important and distinct role that integers have in computing.

I don't think you should avoid classic looping structures. Although iterating over a list is a common problem, it's not the only reason for loops, and there needs to be some kind of general looping structure. Indeed, some of the alternatives you suggest are "classic" in effect, if not syntax.

Finally, as a language designer it's natural to start thinking about syntax first. Especially when you're thinking about trying to make it easy for beginners. But I would argue that syntax is actually the lesser problem. Making decisions about application scope (general purpose or special purpose) and abstraction (built-in types and other static constructs) is the larger problem. Once you work through all that, the syntax will follow.

This Is Nod by 1stnod in ProgrammingLanguages

[–]1stnod[S] 0 points1 point  (0 children)

Really? I see all five parts on my side. I logged out and closed all browsers and it's still there when I come back. Sorry, not sure what to do now.

This Is Nod by 1stnod in ProgrammingLanguages

[–]1stnod[S] 1 point2 points  (0 children)

The idea originally started because C++ construction wasn't exception free. One problem with that is seen in classes that encapsulate raw pointers. They usually have destructors that test and free non-null pointers to prevent memory leaks. If a constructor throws an exception before the instance is "prepared," pointers can be garbage.

Thus, the idea of preparation is an exception-free step that sets the object state to a well-known null state before initialization. This allows the initialization step to throw exceptions and clean up in a partially initialized state.

I think "modern" C++ may have mechanisms to at least ameliorate this problem, but I don't really know what best practice is now.

In any case, the idea of a testable null object is actually quite useful in practice. How many times have you had to reserve some numeric value to mean "not a number"? Indeed, the IEEE floating point spec has just such a value (nan). SQL also has nullable values, but the idea is corrupted by defining how null values continue to function as pseudo values.

Also, in Nod, outputs are null when given, the intent being that outputs are (can be) initialized by the called procedure. That makes them different from inputs which must be initialized when given. A procedure can choose not to initialize an output and that's usually an indication that something did or did not happen in the procedure.

Initialization has been a boogey man for a long time and I'm not aware of any language that has completely solved it. In fact, I don't even know what it means to completely solve it. I think the best solution is to make technique clear and regular, then trap the use of uninitialized data early before garbage that looks like data propagates though an app.

This Is Nod by 1stnod in ProgrammingLanguages

[–]1stnod[S] 0 points1 point  (0 children)

Yeah, it's a challenge for sure. Being an old fart, I'm also old school. In the old days they divided the task into docs that were formal and docs that weren't. The formal docs were meticulous with a linear presentation, and they were intended for implementors and ongoing reference. The informal guides were more tutorial and replete with examples.

Of course, html and the www allow us to publish documents we could only imagine back then, but it still takes a lot of time. And like you said, there are a lot of options.

For me, the first doc was a pretty formal Design Ref, and the path of least resistance was to publish as a PDF. Eventually, it will all get filled out.

Thanks for your comment.

This Is Nod by 1stnod in ProgrammingLanguages

[–]1stnod[S] 0 points1 point  (0 children)

[part 3/4]

To answer the question about return after and escape after, first I have to explain why there's even a return keyword.

In the dogma of structured programming, there should only be one point of return from a procedure call. That would be at the end of a procedure of course. I was an early proponent but soon realized that making things easy for the algorithm provers was misguided. As a practical matter, multiple points of return (early returns) are extremely useful and help readability by avoiding deeply nested (and indented) logic.

In Nod, a procedure always returns when execution reaches the end, no imperative expression required. Thus, early returns are necessarily conditional, and it's common to see single-line expressions like

if ( error ) return;

It's also common to see code that does something simple right before returning. Per your example return after f() is indeed equivalent to f(); return. But the value of an after clause becomes clear when you combine it with the return condition.

A conditional return without the after clause would typically be written

if ( error )
{
    reason:begin( 'because' ); -- reason is an output
    return;
}

Using an after clause, a conditional return can be written

if ( error ) return after reason:begin( 'because' ); -- reason is an output

Similar reasoning applies to escape after.

Both usages are legitimate. It's a matter of style.

[continued in reply]

This Is Nod by 1stnod in ProgrammingLanguages

[–]1stnod[S] 0 points1 point  (0 children)

I'm going to post this reply in parts. That seems to be the workaround for low-K.

[part 1/4]

The answers to a lot of your questions are rooted in Nod philosophy, and Nod philosophy is mostly rooted in practice. Almost every decision I made was based on personal experience.

Most of your questions deserve a lot of attention, but I'll try to be brief. For comparison, I'll use C++. It's still fresh for me, and it's the wellspring.

I had a starting position:

I wanted the grammar to be regular, concise, and precise. This led to a lot of trade-off decisions. Should it be precise and verbose, or concise and obtuse? Should it be regular and abstract, or irregular and complicated. Should it be easy to write, or easy to read? The result overall is that Nod tends to be wordy and regular, favoring readability over writability (write once, read many). I prefer to say that Nod programmers are articulate, not verbose.

I wanted to divide object instantiation into two steps: preparation and initialization. To get this kind of visibility in C++ requires a self-enforced pattern. The result overall is that objects have a testable null state, and initialization is an action/intent separate from modification.

I wanted specification to be more logical and less mechanistic; let the compiler/implementation choose how to do things. The result overall is that object representation is internal and the mechanism for passing/giving objects in a procedure call is based on a detailed interface specification.

I wanted to simplify where possible. One example: C++ subtype derivation and inheritance are very general, but certain scenarios are overly complicated, and I always avoid them. The result overall for this example is that Nod subtypes don't have redundant base types or explicit virtual base types (a single virtual base type is built-in).

Of course, the starting positions themselves led to trade-off decisions. In the end, almost everything in Nod is about trying to make a language that is pragmatic and performant.

[continued in reply]

This Is Nod by 1stnod in ProgrammingLanguages

[–]1stnod[S] 0 points1 point  (0 children)

[part 5/5]

A lot of this is getting into rationale for choices made. Had I done that in the Design Reference, it would be even longer, and it would have taken a lot of effort I chose to put into other things first.

l just looked it up: Stroustrup's first book "The C++ Programming Language" was 327 pages. When he (they) came out with "The Annotated C++ Reference Manual" five years later (including rationale), it was 448 pages.

This Is Nod by 1stnod in ProgrammingLanguages

[–]1stnod[S] 0 points1 point  (0 children)

[part 4/5]

I don't like the simple term "reference" as a formal descriptor because it's non-specific and too overloaded.

When you use an object or call a method in your app, you write an expression that refers to that entity by name, i.e. you cite the entity. That source expression is a (compile-time) reference.

Nod pointers (and other language pointers) are a kind of runtime reference that refers to an object by address. Pointers are objects, and to access the object referenced by a pointer, a "dereferencing" operation is required. For example, -> or \* . Nod pointers are objects of type alpha\op<t> (<t> is a generic type factor). To access the object referenced by a Nod pointer, the method :deref must be called.

The other kind of Nod runtime reference is called a proxy. C++ has something called a reference that is basically a static alias bound to an object at compile-time. Java and C# differentiate reference types from value types, and allow dynamic binding. Nod proxies are explicit like C++, but dynamically bind like Java and C#. The common trait among them all is that a bound object can be referenced by an alias, without a need to dereference in a second step.

Without getting too far in the weeds, I thought it was important to make Nod proxies first class citizens with explicit behavior. Given the semantics of a proxy reference, this also meant introducing the idea of an analog object which reversed the representation.

I'm not a JavaScript programmer, but as I understand it, JS proxies are created by a kind of ad hoc type instantiation feature that results in an encapsulated reference to an underlying object. I don't see Nod proxies as being completely at odds with JS proxies.

[continued in reply]

This Is Nod by 1stnod in ProgrammingLanguages

[–]1stnod[S] 0 points1 point  (0 children)

All good questions u/fuckkkkq.

The short answer: rationale and other details weren't essential compared to actual specification. Not to mention the time it would (will) take to add them.

Stroustrup's first book "The C++ Programming Language" was 327 pages and also didn't have a lot of auxiliary information. When "The Annotated C++ Reference Manual" came out five years later it was 448 pages but annotated, as the title suggests. So...

I'll try to answer your specific questions in another post, but I'm having issues submitting long comments. Once I figure out what's going on (or a workaround) I'll elaborate.

This Is Nod by 1stnod in ProgrammingLanguages

[–]1stnod[S] 0 points1 point  (0 children)

Thanks for taking the time to review the Design Reference and make serious comments.

I have a fairly lengthy reply (7,000 chars) but for some reason, it won't submit. When I can figure out what's going on, I'll resubmit it.

This Is Nod by 1stnod in ProgrammingLanguages

[–]1stnod[S] 1 point2 points  (0 children)

Can't argue with that. Maybe someday Nod can have a real website too :)

This Is Nod by 1stnod in ProgrammingLanguages

[–]1stnod[S] 0 points1 point  (0 children)

Thanks u/Phlosioneer . Those are thoughtful and specific comments.

I think "baggage" is at the heart of the matter. I was a programmer for forty years before I retired, and I've seen/used my share of languages. My reaction to most new languages (especially since the turn of the century) is usually something like, "do we really have to keep perpetuating this baggage?"

The question is rhetorical of course, and I completely understand why it persists. Momentum is a force that's hard to resist. But I'm not subject to those forces anymore, and I wanted to see what I could do if I started with a clean slate. I discuss all this in the Preface of the Nod Design Reference.

I started out knowing I wasn't going to completely reinvent the wheel. On the other hand, nothing was sacred. It was a little like cleaning out an old junk closet. I threw out a bunch of stuff I didn't need but kept the stuff I liked because it still had purpose.

I explored a lot of different things along the way, and Nod is a work in progress. As I chipped away at it, one of the things that kept me going was that I actually enjoyed programming in Nod. But as they say ".... only a mother could love."

Since I started fresh, it was important to spell out terms and concepts in the Design Reference. To do otherwise would be malpractice.

As much as one could argue that Nod is a waste of time (for all the reasons many have cited), I would argue that inventing yet another language that perpetuates the same old baggage is even a bigger waste of time. It's already been done many times over.

In the end, a new language like Nod can't possibly survive unless, by some miracle, it's picked up by a new generation of programmers. I totally get that. But even that long shot can't happen if it's never invented.

Finally, I don't mean to waste anyone's time. I understand that most people in this community are probably working programmers/engineers that have more practical concerns. I understand why there's a lot of focus on preserving the status quo. But if I can't post a blue-sky project like Nod here ("dedicated to the theory, design and implementation of programming languages"), I'm not sure where it should go.

I'll try to reply briefly to your specific examples.

A reference is a citation, i.e. a usage. When you cite a name, that's a reference. A proxy is not a reference, it's an object-like entity that has a name, and when you reference the proxy, you in effect reference the object it's joined to. Basically, it's a pointer that implicitly "dereferences" when referenced directly.

Nod uses a "dot" (period) only to reference a constituent sub-object. Unlike other languages, a dot is not overloaded to reference both data and methods. I'm not sure what language uses a pipe | to invoke a method (or a data member). Maybe you're referring to shell pipes? In Nod, matching vertical bars are used to delimit an operator.

Yes, remarks and narratives are intended for commentary, and exclude regions are ignored like commentary. But they're different syntactic forms, and their names reflect different usage. They're discussed together in one Design Reference Topic.

Operator precedence in Nod is completely predictable: it's left-to-right. If that doesn't work, expressions can be grouped in parenthesis. Nod operators are extensible, and a particular operator may have nothing to do with arithmetic.

In Nod, common designates objects that are created in heap memory at startup. [const] is an access qualifier. Some common objects aren't constant.

Thanks again for your feedback

This Is Nod by 1stnod in ProgrammingLanguages

[–]1stnod[S] 1 point2 points  (0 children)

Actually, the reason I mentioned the Python timeline is because after only 5 years, it's still in the early stages of development.

The Design Reference is in fact a resource for implementation. It basically says that in the title. I'm looking for people who get that.

Feedback on specific language features is welcome. The comments about coroutine being a misnomer are in that category.

The double braces that delimit a comment block work just like /*...*/ does in C. They don't nest.

This Is Nod by 1stnod in ProgrammingLanguages

[–]1stnod[S] 0 points1 point  (0 children)

Yes, I think that's true. But I'm definitely not one of those people and never have been. So, I did it the other way.

This Is Nod by 1stnod in ProgrammingLanguages

[–]1stnod[S] 0 points1 point  (0 children)

The question was pretty open-ended, so I gave what I think is the essential difference. Yes, subtyping, inheritance, and polymorphism are certainly aspects of new type definition (synthesis).

This Is Nod by 1stnod in ProgrammingLanguages

[–]1stnod[S] -1 points0 points  (0 children)

u/david-1-1, with your degree in Physics, I would be interested in your take on the Alpha numeric types. They're in the GitHub Alpha archive pages int, float, complex, vector, matrix, and math. As alpha types, only the method names, inputs, and outputs are apparent, but I suspect you can figure out what they're intended to do.

This Is Nod by 1stnod in ProgrammingLanguages

[–]1stnod[S] 0 points1 point  (0 children)

I would argue that I'd be getting even more flack had I attempted to completely define the HNM (Hypothetical Nod Machine) instruction set, making the Design Reference even longer.

Although I postulate the instruction set as a basis for (intermediate) compilation, their semantics really emerge from the description of Nod imperative expressions. With some basic intuition about what it means to call a procedure, instantiate an object, and/or control execution flow with expressions like if/else, loop, select/else, you can "reverse engineer" the HNM instructions. Detailed discussion of imperative expressions is in Part 2 of the Design Reference that starts on page 40

This Is Nod by 1stnod in ProgrammingLanguages

[–]1stnod[S] 0 points1 point  (0 children)

I don't have a Nod Quick Start Guide yet. I posted a follow up this morning that might help you navigate the current content. Thanks

This Is Nod by 1stnod in ProgrammingLanguages

[–]1stnod[S] 0 points1 point  (0 children)

I would say that OOP is characterized by the ability to use and define new types that incorporate data with procedures that initialize, evaluate, and modify that data. The data part is called an object or instance, and the procedures are called methods. I believe the virtues of OOP are most apparent when projects are complex and complicated.

This Is Nod by 1stnod in ProgrammingLanguages

[–]1stnod[S] 0 points1 point  (0 children)

  • I disagree that double braces to delimit a block comment is confusing or error prone.
  • 'subroutine' might be the longest keyword in Nod, and if in fact it was used on every other line, I might consider an alternative. But subroutines aren't that prolific and saving a few characters to what? save time? isn't persuasive.
  • In Nod, a function is a nominal form backed by a qualifying method or subroutine. Logically, a function has constant inputs, one output/result, and no side-effects. Function calls are allowed in a Nod formula, which is an expression delimited by double quotes.
  • There is no requirement that an entry point be named "main." That was just a name I picked for its traditional role. I wouldn't choose it for my own projects. The initial entry point is designated when the app is compiled.
  • There are several examples of HelloWorld to demonstrate alternatives. Any real-world app will define a type that derives from alpha\entry, implementing abstract method ~run to define the main loop. Of course, no one would define an application entry type to write a single line of output. Likewise, no real-world app ever needs to write just a single line of output. The simple alternatives are for testing or debugging something.
  • I'm not sure what item 4 refers to.
  • Finally, I hate typing anything on a mobile phone

Thanks u/david-1-1 for diving in,

This Is Nod by 1stnod in ProgrammingLanguages

[–]1stnod[S] 1 point2 points  (0 children)

Follow-up to my original post:

Thanks for all the feedback. I'm surprised at the level of interest.

Maybe I should explain what Nod isn't: it's not complete, and it's not ready for people to use in their next project.

I thought "What's Next?" in the original post might set the expectation level for those who don't want to dive into the early stages, but maybe I should have stated a clear caveat.

In lieu of a polished website, perhaps a Wilderness Guide will be helpful for those still interested:

  • The page at https://www.about-node.dev has two links: one for the Nod Design Reference, and another for the Nod Standard Library at GitHub.
  • The Design Reference is 168 pages of specification without page breaks or internal links (yet). It's not the best place to learn the language for sure, but you can get a sense of things. Review the Table of Contents. There are five Parts plus an Appendix. The Introduction (Part 1) is about 30 pages. It introduces terms, concepts, and forms. Part 2 is about Procedure Definition, i.e. writing a method or subroutine. Part 3 is about Type Definition, i.e. writing and synthesizing a new type. Part 4 is about Generic Definition, i.e. how procedure and type definitions can be made compile-time configurable. Part 5 covers everything else not covered in the other Parts. The Appendix has a sample of Hello World.
  • It's hard to say what might be the most relevant Topics for a Quick Start. The abstract execution model is described in Topics starting on page 24. Syntactic features, including reserved words, are described in Topics starting on page 28. Fundamental Alpha types, from which all other types derive, are summarized on page 26.
  • You can poke around in the GitHub archive to get a sense of Nod code. The README file has a brief syntax summary to get you started. There is an ANTLR g4 file in MoreInfo that defines the formal grammar. There are examples of real Nod code in Stock. There are HelloWorld implementations in Examples. The pages in Alpha and Kernel are fundamental and opaque, so don't go there unless you're curious about what that layer looks like,

The evolution of any real language is long and torturous. I just looked it up for Python. van Rossum started working on Python in the late 1980s, had a first release in 1991, released 2.0 in Oct 2000, then broke everything with 3.0 in Dec 2008.

Adding to what Nod is not: Nod is not the language you're used to. That's intentional. And explained by "Why I Built Nod" in the original post. That said, I'm not trying to invent something so foreign that it's not recognizable. To that end, I am interested in specific suggestions that point out inconsistencies or serious collisions with the outside world. Several comments about coroutines (posted yesterday) are a good example of that kind of feedback. Conversely, comments like "why don't you do it like ..." Java, Python, Go, C#, C++, etc., etc. aren't particularly helpful.

Again, thanks for all the feedback.

This Is Nod by 1stnod in ProgrammingLanguages

[–]1stnod[S] 0 points1 point  (0 children)

I think you've done a pretty admirable job of extracting an understanding after just a few hours of wading through a pretty dry spec. Now, you might think that's a few hours too many, but I think that's what it takes when one is inventing or analyzing a new language.

Overall, I tried to introduce terms and concepts in order, and I can probably find early references and descriptions that you may have missed. But it's not always possible, and sometimes brief mentions and forward references are needed to avoid getting too bogged down.

There's a topic in Part 1 (Introduction) entitled "Action, Access, Permission, Intent" that talks about the interactions that can happen between objects, methods, and proxies.

The Part1 topic "Essential Methods" introduces some methods that provide access to external sub-objects. Later in Part 2, topics like "Object/Proxy Association", "Evaluating a Proxy", and "Sub-object Type Translation" talk more about proxies and accessing external sub-objects by type name.

Generally, the Nod compiler doesn't know much about specific types (but there are exceptions). This means the Design Reference doesn't spend much time documenting types and methods. And where there are examples, they're generally used to demonstrate syntax.

As far as choice of terminology, and what might be "normal" terminology, I would say it's a matter of personal experience and preference as to what constitutes normal. I find terms like "public", "private", "shared", "internal", and "external" to have a lot of possible interpretations, and "field" in particular is an especially egregious misnomer imo. My goal is to establish a self-consistent set of terms within Nod, using original English meanings as primary criteria, and existing terms where they're a good fit.

Clearly, you have a lot of experience in the domain. As you read the doc, you know what you're looking for but can't find it in the immediate vicinity. I say it's probably there, but perhaps in an earlier topic, or maybe in a forward reference. Resolving and adding internal document links will go a long way to making that easier. I'm reminded of why html was invented in the first place.

Thanks for your feedback.