Hacker News new | past | comments | ask | show | jobs | submit login
Cone Programming Langauge (jondgoodwin.com)
79 points by adamnemecek 19 days ago | hide | past | web | favorite | 47 comments

While I like a lot of the ideas described on the website, I don’t like how they’re written in the present tense and like Cone can currently do all these things when in fact they’re just aspirations and aren’t even designed in detail. I had to go to a file in the Github to figure out what actually exists.

I support your quest but actually implementing the language is the hard part. Languages like Rust don’t even mention features on their websites even when they have working implementations that just aren’t stable yet, it would be nice if Cone’s gave a clear picture of what exists and what’s an aspiration.

Cone is developed as a proof of concept for the author's paper Gradual Memory Management[1]. The author is experienced in language development and has previously written Acorn. It was seen as more appropriate to write a new language rather than trying to tack the idea onto an existing language.


If maximum (or just more) flexibility wrt memory safety is the goal, I might suggest the author take a look at SaferCPlusPlus. In particular, it supports memory safe pointer/references[1][2] that can target objects associated with different allocators (including stack allocated objects) without, as the author desires, imposing the "sometimes confining" restrictions that Rust does. And in terms of data race safety, it allows you to hold "read-lock" and "write-lock" pointer/references simultaneously in the same thread [3], which adds a little extra functionality (and maybe convenience).

[1] shameless plug: https://github.com/duneroadrunner/SaferCPlusPlus#registered-...

[2] https://github.com/duneroadrunner/SaferCPlusPlus#norad-point...

[3] https://github.com/duneroadrunner/SaferCPlusPlus#tasyncshare...

It's good that you include "shameless plug" in these posts to clarify, but it would be much much clearer if you included more explicit phrasing along the lines of "I only post to HN to plug my project called SaferCPlusPlus, not ever to talk about the submitted article. You can see here: https://news.ycombinator.com/threads?id=duneroadrunner."

> I only post to HN to plug my project called SaferCPlusPlus

From this account, yes. Too much? Sorry, (you can see) I haven't gotten much feedback. Or is having a separate, project-specific account in itself not cool?

> not ever to talk about the submitted article.

I try to post/plug only when I think it's relevant. I think. For example, in the "gmm.pdf" linked in the comment I responded to, the author says that specific types of references can only target objects owned/allocated by the associated allocator. There is no available reference type that can target objects from different allocators. References in Rust, for example, can target any object regardless of how they were allocated, but imposes strict restrictions that the author implies he's trying to avoid.

So I tried to point out that SaferCPlusPlus has pointer types that can safely target objects allocated by different allocators and do not have the strict restrictions of Rust's references. As far as I know, these types of pointers are (still) unique to SaferCPlusPlus, and I assume I am one of a few people who is familiar with these pointers. But there's nothing proprietary about them. If the author is constructing a language with a goal of flexibility wrt to memory safety, I thought he might consider whether such pointer/reference types might be compatible with his language design. I think they unquestionably increase flexibility (while maintaining memory safety).

> There is no available reference type that can target objects from different allocators.

Cone actually does support Rust-like, lifetime-constrained borrowed references which can do exactly that safely. Cone also supports raw pointers (however de-reference safety is the responsibility of the programmer).

I appreciate the chance to learn about your language's unique form of reference type. I am less likely to call them safe than you, no doubt because I use a different criteria for safety. A key requirement I have placed on references (vs. pointers) is that you can always de-reference them and get a valid value with no chance of exception. I don't think your references would comply with this.

A Cone programmer would need to use raw pointers to throw off the shackles of lifetime constraints but, unlike with your references, they could not expect such pointers to turn into nullptr if the object they refer to has been freed. Given the nature of Cone's design, there is no way to accomplish this mechanic with decent performance, especially given that borrowed references and raw pointers are both able to point inside an allocated object.

I do appreciate your bringing it to my attention and wish you all the best with getting others to learn about and adopt your language.

> Cone actually does support Rust-like, lifetime-constrained borrowed references

Ah, so kind of a super-set of Rust functionality. Presumably these would require a "borrow checker" or equivalent? Is that already implemented? So how do you address the safety of, say, taking a reference to an element in a (resizable) vector? Rust's "exclusivity of mutable references" restriction intrinsically makes the vector immutable while the borrowed reference exists, but do I understand that Cone doesn't have that restriction? The "C++ lifetime profile checker", on the other hand, makes the vector non-resizable (but leaves the data mutable).

> A key requirement I have placed on references (vs. pointers) is that you can always de-reference them and get a valid value

SaferCPlusPlus provides both a pointer that throws an exception if you attempt an invalid memory access (though it could just as easily return an optional<>, and you can always query if the target is valid), and one that terminates the program if its target is ever deallocated prematurely (and thus (technically) satisfies your criteria). (The latter has less/minimal overhead.)

> A Cone programmer would need to use raw pointers to throw off the shackles of lifetime constraints

Or reference counting pointers or GC, right? The features needed to implement the pointers I mentioned is either support for calling a destructor on move operations or the ability to make an object non-movable, and, support for copy constructors or the ability to make an object uncopyable. Does/could your language support some combination of those features?

I explain the reason the pointers are important in an article called "Implications of the Core Guidelines lifetime checker restrictions" [1]. Specifically, I give an example of reasonable C++ code [2] that historically had no corresponding efficient implementation in Safe Rust. (I think it's still the case, but I haven't fully investigated the implications of Rust's new "pinning" feature.) It can, however, be implemented in a memory safe way using the SaferCPlusPlus pointers in question [3]. Basically the example just temporarily inserts a reference to a (stack allocated) local variable into a list (or whatever dynamic container), given that the local variable does not outlive the container.

> especially given that borrowed references and raw pointers are both able to point inside an allocated object

SaferCPlusPlus has the equivalent of "borrowed references"[4] (though even more restricted until C++'s "borrow checker" (the aforementioned "lifetime profile checker") is completed), and they can safely point "inside" (allocated) objects. Note that the second safe pointer type (the one that potentially terminates the program), is a "strong" pointer, and there is a simple mechanism for obtaining a "borrowed reference" from a strong pointer [5]. And from there, a simple mechanism for obtaining a reference to an (interior?) member [6].

The other pointer (the one that potentially throws an exception) would be considered a "weak" pointer, and you cannot obtain a "borrowed reference" directly from a weak pointer. But often, the weak pointer is used to target an object that can yield a borrowed reference (like a strong pointer, for example), or a borrowed reference directly.

> all the best with getting others to learn about and adopt your language.

You too :) I can see the appeal of this sort of clean, flexible language. But in the case of SaferCPlusPlus the goal is not necessarily (just) for programmers to adopt it. In part it's maybe a demonstration (to language designers such as yourself :) of a set of language elements that use run-time safety enforcement mechanisms but are a little more flexible than (and might be a good / (unintuitively) needed complement to) their counterparts that rely on strictly compile-time safety enforcement.

Oh and if you do get around to checking out SaferCPlusPlus in more depth, apologies for the inadequate documentation in advance. Feel free to post any questions you might have. :)

[1] https://github.com/duneroadrunner/misc/blob/master/201/8/Jul...

[2] https://github.com/duneroadrunner/misc/blob/master/201/8/Jul...

[3] https://github.com/duneroadrunner/misc/blob/54204445bc099987...

[4] https://github.com/duneroadrunner/SaferCPlusPlus#txscopeitem...

[5] https://github.com/duneroadrunner/SaferCPlusPlus#make_xscope...

[6] https://github.com/duneroadrunner/SaferCPlusPlus#make_pointe...

Yes, borrowed references being lifetime-constrained means that I have a "borrow checker" that ensures that. It is only partially implemented.

You are correct that Cone supports a static, shared, mutability permission, including on borrowed references into resizable arrays. The short safety answer is array resizing is only possible when you have a unique reference to the array, so you can't run into the trouble you describe. I wrote a post about it.[1]

You left out an important clause I specified in my criteria: "with no chance of exception". Terminating the program in the event of dereferencing a reference does not meet the safety requirements I set for Cone references.

Yes, only borrowed reference have lifetime constraints. I did not mention the allocator-based reference in that quote because of context. Cone does support a distinction between move vs. copy types. Unlike with Rust, the distinction is typically inferred from the definition of the type. Currently, all memory is "pinned", but that may become more flexible in the future.

The safety strategy for Cone involves versatility: giving the programmer a curated collection of permissions and memory allocators, each with distinct advantages and disadvantages. The safety of certain options can be completely determined statically, making them inflexible but fast. Others will use a mix of static and runtime mechanisms, which offer greater flexibility but incur a runtime cost.

That said, I admit I am somewhat uncomfortable temporarily injecting a borrowed reference into a longer-lived container as snippet 4 shows. I feel like any logic able to ensure this is only done safely would be too complicated for my taste, at least for now. I understand how your mechanism would address this scenario, but again that does not ascribe to my more restrictive notion of safety. If the program does it wrong, it crashes.

Thanks for the added insight into your design choices and your kind comments. I will certainly look through your links. [1] http://pling.jondgoodwin.com/post/interior-references-and-sh...

> I wrote a post about it.[1]

I just read it, and I thought it was great. I had a similar, if perhaps not-as-well-thought-out, reaction to Manish's (I agree, excellent) post. I think SaferCPlusPlus basically implements the permission mechanisms you listed in the summary (as well as the preceding "Race-Safe Strategies" post). (Although with some of the restrictions enforced at run-time rather than compile-time.) Looking forward to Cone 1.0. :)

p.s.: btw, the link on your post to the preceding "Race-Safe Strategies" post is broken

Did you also read the follow-on post on transitional permissions?

No, I just read it. SaferCPlusPlus does support all the permission "modes" listed, except the "opaque" one, and transitioning between them (though being stuck with C++'s move semantics). The permissions modes that aren't intrinsic to C++ are enforced at run-time. Objects to be mutably (non-atomically) shared need to be put in an "access control" wrapper, which is kind of like a generalized version of Rust's RefCell<> wrapper.

As I noted in my original comment, in the "mutex"/"RwLock" case, SaferCPlusPlus allows you to simultaneously hold read-locks and write-locks in the same thread. Which seems natural, since SaferCPlusPlus (and Cone) allows const and non-const pointer/references to coexist in the same thread. But in this case it actually provides increased functionality. It is the functional equivalent of replacing your mutex (and Rust's RwLock) with an "upgradable mutex", which facilitates better resource utilization in some cases, right? It also provides new opportunities to create deadlocks, so the mutex has to detect those.

Btw, I am certainly a pot talking to a kettle here, but your "mutex1" urgently needs a better name, right?

Absolutely agree.

You've got some stray punctuation that breaks your link.

Fixed link for the lazy: https://news.ycombinator.com/threads?id=duneroadrunner

That's a fair critique, and I will edit the website to make that disconnect between current reality and aspiration clearer (more than the disclaimer I have on the home page). Because there is so much left to do, I have made no effort to market the language for any sort of use. So, I got caught flat-footed that a website I am still building up piece by piece, primarily for personal clarity and private communications, has gotten premature publicity. I will adjust accordingly.

>I have made no effort to market the language for any sort of use.

Didn't you say it was mainly for the 3d web or something?

Yes, but that's not what I meant in that quote. Cone is not ready for any use right now. So, I am making no effort to market it right now. When it is at least MVP (at least a year or more away), I will get more serious about marketing, including especially for 3D web.

Thanks and good luck with your project!

I disagree, a language is merely a standard defined by reference documentation and sometimes certified by ANSI and the like. The fact that things are documented means thought has been put into the design, but... yea unimplemented things should be labeled

I think it used to be the case that a language and it's implementation were considered separate things. Today it seems like they are conflated to the point where people take the position that if it's not implemented, it's not actually a language. This view discounts the great languages of the past that were never implemented due to platform immaturity.

I think the issue was more that languages often used to have multiple implementations which differed and no real standard beyond the de facto (whichever was most commonly used).

You saw this with the variations of BASIC on the micro computers, the different Pascal and C compilers of the 70s and 80s etc.

But even back then, languages without a compiler were still nothing more than an academic exercise which entertained a comparatively few of the overall engineers around at that time.

So I don’t think much has changed in that regard aside the expectation that a language should now have a reference implementation. And even then that’s not always the case (eg Perl 6).

The language description declares it is safe, but since you can dereference raw pointers, the language is not memory safe. Maybe it should be made clear that you are not claiming memory safety.

It is memory safe in the same way as Rust is. Rust allows you to de-reference a raw pointer, but only within an explicitly de-marked `unsafe` block. Cone will require a similar explicit mechanism. If you want the compiler to check your code for memory safety, don't use this mechanism. If you do use it, the responsibility for safety within rests on you, the programmer.

On it's own, pointer dereffing is not unsafe. Null pointers and out of bounds dereffing is what causes problems. Unless you're in an unsafe block I don't think Cone allows for arbitrary pointer shenanigans like C does.

Interestingly, the logo is a direct copy of UCF's academic logo: https://www.ucf.edu/brand/brand-assets/logo-identity-system/. I wonder if there is a connection.

There is no connection. I was unaware of the similarity to UCF's logo until recently. It is something I will need to replace, as much as I like how it looks.

No matter how enthusiastic the creator is about a “3D web” I doubt putting it as a top design goal does the language any good (I nearly stopped reading right there).

Perhaps as the language seems to be influenced by Rust it would be good to outline what specific differences it has to Rust in terms of goals.

A summary of key differences from Rust: A richer collection of build-in memory management strategies (e.g., tracing GC), Pony-inspired permissions (esp. safe shared, mutable), structural subtyping (as well as nominal), and a bunch of features intended to improve ease-of-use and productivity. At some point, I will write up a page on my web site to explain Cone better for someone coming from Rust (or C++ or ..).

Very cool language. It is good to see people still trying to innovate! However, the marketing seems like a contradiction - concise and readable are antithetical as well as "flexible typing" and type safe. I am left confused.. what paradigms does it employ? Imperative?

>concise and readable are antithetical

Not really. If anything concise core can be much easier to read. The key is to discard BS boilerplate and ceremonial code, and have the intention of the code clearer in less lines.

Python is far more readable than Java, for example, and far more concise.

>as well as "flexible typing" and type safe

Not if you support variant types and the like.

Hmmm, you assume everyone understands that fn means function and that a void function is the same as a procedure. Everybody and their grandma can read Pascal and Ada, but abbreviated keywords and magical symbol require foreknowledge. And the more abbreviated, the more you will have to continuously look up things in the reference manual until they get cemented.

> Not if you support variant types and the like.

Not sure what you mean here - discriminant unions? classes?

>Hmmm, you assume everyone understands that fn means function and that a void function is the same as a procedure.

No, I don't assume that. But I think that that's irrelevant. People didn't know "def" means function in Python or Scala either, but they learn it on the first day and don't have a readability problem with it.

Very basic keywords like "fn" are not the things that make a language unreadable, nor is calling the keyword "fn" instead of "function". Those are memorized in a day or so and you're done with it.

It's other things that make languages unreadable (too many sigils, overloaded operators or keywords in different contexts (C++, Perl), single letter operators (e.g. APL), too low level, too much verbosity, etc.

In any case, whether a person that first encounter a language immediately recognizes what a keyword means is not what makes the language readable. It's how readable it is to its programmers, after they know the language and have written code in it that matters. Familiar != readable.

>Everybody and their grandma can read Pascal and Ada, but abbreviated keywords and magical symbol require foreknowledge.

And almost everybody and their grandma dislike their syntax (especially Ada's), and would prefer something less verbose.

>And the more abbreviated, the more you will have to continuously look up things in the reference manual until they get cemented.

It's after such basics "get cemented" that readability comes into play, not before. If someone has to look after 2-3 days with the language what "fn" means or how to declare a function (and similarly, "def" in Python/Scala, etc), they have some serious memory issues.

(Btw, Rust, Clojure, and others also use "fn").

>Not sure what you mean here - discriminant unions? classes?

Yes, aka variant, aka tagged union, aka sum type...

> overloaded operators or keywords in different contexts (C++, Perl)

Woa there, ever look at vector math libraries in C - horribly unreadable and ugly.

> too much verbosity

Verbosity describes a programs behavior without implied knowledge thus a reader of some code will understand exactly what is meant. I disagree that the definition of readability is all about how quickly you can skim source code - and ew too many words.

> And almost everybody and their grandma dislike their syntax (especially Ada's), and would prefer something less verbose.

Well, they were objectively designed to readability based on studies of other languages (at least Ada). And hey, I like it but I guess I am biased : P

>Verbosity describes a programs behavior without implied knowledge thus a reader of some code will understand exactly what is meant.

Readability is not just about "understanding what is meant if you take the time". It's also about each piece of code being easy to scan and understand to find what you need. Devs have to scan whole projects to find what happens where, and there's a balance between cryptic very succinct statements and getting lost in expanded verbosity.

Verbosity hurts that by making less code fit on your screen, the code having more stuff that's needed to convey the meaning, etc. You get lost in the extra details, and have more baggage to keep in mind.

It's the difference between saying, e.g.: "Yeah", and "I am indeed craving something savory with lots of melted curdled milk and processed pork cuts, and I believe that we can immediately proceed with the thing that we were just now discussing to do".

Which is one is the more "readable" answer to "Wanna go for a pizza?".

Very well said. This is very much what I am after. Some common idioms in Rust or C++ just feel unnecessarily verbose to me (e.g., `<exp>.unwrap().borrow_mut()`), and I am looking for straightforward ways, often borrowed from other languages, to express the same intent more succinctly and clearly.

"And the more abbreviated, the more you will have to continuously look up things in the reference manual until they get cemented."

But something like "fn" isn't going to be a problem, because you're going to use it a dozen times before you finish the tutorial. Symbols are also OK in proportion to their commonality. Rarely used symbols become more problematic. This can be mitigated if there's a clean way to find documentation for them, though you can't get around the problem that google is unlikely to ever be useful for them.

This is actually a property in information theory. Often used symbols should have short representations and rarely used symbols should have long representations.

People have a habit of automatically doing this. It's why pretty much every niche field or industry develops jargon and three letter acronyms. Everyone gets tired of saying the whole thing so it's shortened.

Not only does it just make sense that things like "fn" are going to be okay, but there's also some mathematical backing as to why it's okay.

Sure, but my point is that "Okay" things are not as good as "Great" things like spelled out type definitions.

For example look at this object declaration in Ada - you may not know Ada and the symbols associated with pointers but you will know exactly what it is:

    type My_Arr is array (1..10) of Natural;
    My_Fancy_Obj : not null access My_Arr :=
      new My_Arr'(1..4 => 0, others => 1234);

"type", "array", and "Natural" are all specialized computer science or mathematical terms. "My_Arr" is just as mysterious as the rest of them and unless you're familiar with variable binding, you'll just lump that symbol in with the rest of them (some unknown sigil of magical power). I suspect most people will have a good idea of what "is" is and of what "of" is. I bet most people would also understand ranges, so 1..10 is probably fine as well.

So, what your first line should be if we were really spelling things out:

classification definition named: "My_Arr" is: "list (1..10)" which will contain: "numbers zero and greater but without decimals"

But you aren't spelling things out. You're using specialized terms to reduce the amount of typing you have to do because the terms in question happen to come up frequently. Welcome to the club.

    type My_Arr is array (1..10) of Natural;
Speaking as someone who works in PL research (but keeping in mind that I'm still low on the totem pole, i.e., I don't have a PhD)... I don't know what this is supposed to mean.

Based on the first word being `type`, I'm guessing this is a type declaration for a new type named `My_Arr`. And `is array` makes me think that this is an array of some kind. But then I get to `(1..10)` and I'm lost. Is this a dependent type? Is it an initialized value? I'm genuinely not sure. The `of Natural` at the end tells me that `My_Arr` is an array of natural numbers, I guess, but I'm lost on the `(1..10)` bit. I figure it could either be:

a) `My_Arr` is the type of arrays of natural numbers of length 1..10 (is that inclusive? exclusive? unsure) or b) `My_Arr` is the type of arrays consisting of only the natural numbers 1..10 (again, unsure whether inclusive or exclusive)

Or I guess it could be something else, though I'm not sure exactly what that'd be.

    My_Fancy_Obj : not null access My_Arr := ...
I guess this is defining an... object? It can't be a type declaration because there's no `type` keyword (which I had previously assumed to indicate the beginning of a type declaration). But this syntax is strange. I assume `My_Fancy_Obj` is the name of the instance in question, so `x : y := z` must mean "create an object named x of type y and store the result of evaluating z there". (I'm guessing based on what I know of other languages.)

`not null access` is weird to me. I mean, "not null" is straightforward enough (`My_Fancy_Obj` can never have a null value), but I'm not sure what "access" means. Is it a reference type? Like a pointer? Or something? I dunno what else it could be.

So `My_Fancy_Obj` is really a box (reference/pointer) containing a `My_Arr`, and it can never be null.

Now... looking at the initialization...

    ... new My_Arr'(1..4 => 0, others => 1234);
I'm so lost haha.

I guess the values 1..4 (inclusive? exclusive? shrug) are initialized to zero. And the "others" are initialized to... the value 1,234? I guess? But how long is this array? The presence of an `others` keyword (I'm assuming it's a keyword, or an initialization argument I suppose) tells me that it must be obvious to the interpreter/compiler exactly how many values there are. So... I guess that means `My_Arr` is actually an array of exactly length 10? Why on earth would they have the `array (1..10)` syntax for that, instead of like `array 10` or `array(10)` or something. That's confusing.

So I guess we end up with something like `[0, 0, 0, 0, 1234, 1234, 1234, 1234, 1234, 1234]`?

Anyway, this example did not achieve your desired result — at least, not from my perspective.

Very good, the code is not even valid indeed. Thanks for making me think :)

No harm meant, friend. :)

>People have a habit of automatically doing this

Which is also tied into a discussion a few days ago, how the most common verbs (e.g. be) are much shorter and irregular in most languages -- because they are fetched from a direct, small, cache of readily usable forms, and not supposed to be derived from the general rules (e.g. adding -ed) like common verbs (which is slower).

This property reminds me of Huffman code.

Yup. If you checkout the wikipedia page on huffman coding you'll see that David Huffman got his idea while in an information theory class with Robert Fano. Fano had worked with Shannon (the inventor of information theory) to develop a similar coding, but Huffman was able to develop a provably better system.

Cone is an imperative, multi-paradigmatic language in the family of systems programming languages (C, C++, Rust, D).

It is fundamentally statically-typed. However, a language that is too rigidly typed can sometimes degrade programmer productivity. Abstractions like variant types and various forms of type polymorphism can improve code flexibility and reuse, which is why I plan for Cone to include these features (the home page has a link to a page that describes this in more detail).

High-level attributes of a language are always hard to get right, because terms mean different things to different people, but several responders do a better job than I did of explaining why concise and readable don't necessarily have to be contradictory goals, at least for me. My primary aim here is that code be maintainable. By concise, I am actually far more focused on how much of a function's logic can fit readably on the editor window without scrolling than I am the size of keywords. Certain common C++/Rust patterns feel unnecessarily verbose to me (e.g., borrowing a reference in Rust), and I am trying to incorporate syntactic patterns from a number of other languages that I feel encode the same intent consistently in a way that a programmer familiar with Cone will be able to process and edit more quickly. Of course, that's always a judgement call...

As a language designer myself I truly do applaud your effort. I am just a fellow skeptical human who loves strongly typed languages!

Keep it up, I will be following the progress.

> ...focused on how much of a function's logic can fit readably on the editor window without scrolling than I am the size of keywords

This seems like a silly reason, we have large widescreen high res monitors - C's curly braces were invented to fit inside 70 char by 90 char terminals. Lets just be honest with ourselves and say its because its what people generally like and/or are used to these days : )

I mean Pascal/Ada isn't all that bad - it just not trendy https://github.com/AdaDoom3/AdaDoom3/blob/master/Engine/neo-...

Thanks. I always welcome feedback and take it seriously.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact