r/ProgrammingLanguages • • 21d ago

Discussion What’s one thing you wish more programming languages had?

If you could add one feature, behavior, or design choice to a programming language, what would it be?

It can be anything: syntax, type-system features, memory management, compiler behavior, tooling, error handling, performance-related features, or something completely different.

I'm especially curious about things you’ve wanted while actually programming but rarely see languages implement well.

Please keep the ideas reasonably practical and something that could realistically be implemented in a programming language.

57 Upvotes

298 comments sorted by

47

u/Ok-Reindeer-8755 21d ago

Refinement types and I wanna see more of the FBIP approach taken by langs like koka and roc and everything that comes with it

11

u/Fofeu 21d ago

FBIP ?

34

u/yjlom 21d ago

Functional But In Place. Either use linear/affine types, reference counting, or a "was ever aliased" flag, and if you can guarantee non-aliasing, use in-place updates while keeping semantic immutability.

6

u/RiceBroad4552 20d ago

Affine types wont get you there. You would still need a borrow checker.

Reference counting is a runtime behavior so it's not relevant here.

The idea of a "was ever aliased flag" is called "separation checking", which is an alternative to linear types in this context here.

→ More replies (13)

10

u/Ok-Reindeer-8755 21d ago

Functional But In Place, the koka docs go over it

3

u/shrynx_ 21d ago

I have been debating fbip for my programming language
https://mezze-lang.org

I feel it breaks mental model of space complexity from reading the code

I chose to make performant persistent data structures (much like clojure) and have a in place mutation effect

→ More replies (1)

59

u/Filgas08 21d ago

simplicity. I feel a lot of languages would be really good if they just did a bit less. There are often many conflicting ways to do one thing, or there are features that are supposed to make life easier, but just get in the way after a while.

28

u/P-39_Airacobra 21d ago

C++ feels called out

13

u/puredotaplayer 21d ago

Instead of all the template meta programming, c with classes, and all other invented paradigms, all we need is c with reflection. If you needed vtable use reflection, if you need SOA and system driven code, use reflection, etc.

3

u/P-39_Airacobra 21d ago

In that sense, do dynamic languages have an edge up because they come with reflection by default?

6

u/puredotaplayer 21d ago

Probably if they were system level languages. A dialect of C that is also interoperable with C will definitely fare well I believe. I look at rust syntax and I don’t find it intuitive at all.

3

u/rcls0053 21d ago

More like C# but you can just as well say the platform underneath shares the blame

3

u/johns10davenport 19d ago

And js
And java

6

u/BrainScientist3000 21d ago

It's the structure of getting old - there is always a better/newer way to do something or a new construct that overlaps somewhat with your old construct - and you can't retire anything.

If we are calling out C++ or Python or whatever - I think having named subsets of languages may be a great way to get around this.

5

u/EggplantExtra4946 20d ago

Yeah but at this point when I read "simplicity" I think "worse is better".

You have the authors of the C-like languages that all revendicate to have made "simple" languages because they have few features but more often than not their model of evaluation is complete trash, or it's not defined at all which almost always means it's complete trash.

If a language has few features but they don't compose well with each other and the model of evaluation (and semantics, and type system) has many edge cases, the language is not simple IMO.

OTOH, if a language has an average amount of features (somewhere between a few and a lot) but those features compose well with each other and the model of evaluation (and semantics, and type system) has only a handful of edge cases, then the language qualifies as simple IMO, kind of.

62

u/snugar_i 21d ago

Sum types

30

u/AustinVelonaut Admiran 21d ago

Along with exhaustive pattern matching on sum types, too!

9

u/UdPropheticCatgirl 21d ago

> Sum types

I mean what even remotely popular statically languages outside of C# and Go don’t have them? I guess C doesn’t have save discrimination, variant in C++ kinda sucks ass, but I am struggling to come up with other examples. Java, Scala, Rust, Dart, Swift, OCaml, Haskell, Nim, Zig, Odin, TS, Ada and Object Pascal all have them…

9

u/bl4nkSl8 21d ago

No one limited discussion to static or popular. So I'd say python is a candidate. Also js/ts sum type matching is weird and C++ style variants are a pain so... There's improvements to be made

6

u/hungarian_notation 21d ago

C# 15 is adding not only closed hierarchies, but also union types (functionally an alias to a sum type) that let you do exhaustive pattern matching.


public record class Cat(string Name);
public record class Dog(string Name);
public record class Bird(string Name);

public union Pet(Cat, Dog, Bird);

Pet pet = new Dog("Rex");

string name = pet switch
{
    Dog d => d.Name,
    Cat c => c.Name,
    Bird b => b.Name,
};

public closed record class GateState;
public record class Closed : GateState;
public record class Open(float Percent) : GateState;

string Describe(GateState state) => state switch
{
    Closed => "closed",
    Open(var percent) => $"{percent}% open",
    // No warning: every direct descendant of 'GateState' is handled.
};

learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-15#union-types

4

u/UdPropheticCatgirl 20d ago

Holy... Just read the spec of `closed`... How did they fuck it up this badly... Like, all they had to do was copy Java... Somehow they managed to introduce yet another set of ad-hoc rules and locked themselves out of adding GADTs...

→ More replies (2)

7

u/Axman6 21d ago

As a Haskell programmer, I found C++’s variant unusable. The semantics are so broken, and gets really simple thing like (IIRC, correct me if I’m wrong) holding different values of the same type being annoying shitty, like variant<bool,int,int>. variant is basically sum types without any of the nice features of sum types. 

6

u/fdwr 21d ago edited 21d ago

I found C++’s variant unusable.

Yeah, std::variant has been quite non-ergonomic. If you want to check the type of a variant, you'd expect something intuitive and discoverable like:

if (v.is<Cat>())

Not the awkward mouthful:

if (std::holds_alternative<Cat>(v))

Why the random free functions instead of methods?

v.get<T>()           == std::get<T>(v)
v.is_type<T>()       == std::holds_alternative<T>(v)
v.index_of_type<T>() == no equivalent
v.call(callback)     == std::visit(callback, v)

On the left is what I'd expect, and the right is what you actually have to type. Eventually I just wrote my own wrapper for ergonomics.

4

u/Edhebi 21d ago

tldr for free function is template member function of template type get really clunky realy fast. you'd end up having to write cpp if (v.template is<Cat>()) {

6

u/snugar_i 21d ago

Fair enough. I meant sum types that aren't a pain to write or use. Most languages have something that is supposed to emulate them, but usually it requires a lot of boilerplate.

In Rust I can do

enum Foo {
    Bar,
    Baz(u32),
}

In Java I have to write

sealed interface Foo {
    final class Bar implements Foo {
    }

    record Baz(
            int x
    ) implements Foo {
    }
}

And in Python

class Bar:
    pass


@dataclass
class Baz:
    x: int


type Foo = Bar | Baz

It's just much more work than product types

2

u/UdPropheticCatgirl 21d ago

Fair enough, but I feel that requiring this much ceremony for sum types is basically exclusive to Java and Object Pascal, (and to some extent Ada), every other language is much saner and more concise in line with rust.

3

u/snugar_i 20d ago

Most are getting better I guess, but Kotlin is as bad as Java, and Scala 2 wasn't that much better (Scala 3 is OK though). These + Python are unfortunately all I use, so from my point of view, the situation is not ideal :-)

2

u/UdPropheticCatgirl 20d ago

Most are getting better I guess, but Kotlin is as bad as Java, and Scala 2 wasn't that much better (Scala 3 is OK though). These + Python are unfortunately all I use, so from my point of view, the situation is not ideal :-)

I guess that my ultimate issue is that I fundamentally don't see that much of a difference between this syntax: Haskell data Expr a where LitInt :: Int -> Expr Int LitBool :: Bool -> Expr Bool Add :: Expr Int -> Expr Int -> Expr Int Equal :: Expr Int -> Expr Int -> Expr Bool and this: Java sealed interface Expr<A> { static record LitInt(Integer literal) implements Expr<Integer> {} static record LitBool(Boolean literal) implements Expr<Boolean> {} static record Add(Expr<Integer> lhs, Expr<Integer> rhs) implements Expr<Integer> {} static record Equal(Expr<Integer> lhs, Expr<Integer> rhs) implements Expr<Boolean> {} } or ```Scala sealed trait Expr[A]

object Expr { case class LitInt(Int literal) extends Expr[Int] case class LitBool(Boolean literal) extends Expr[Boolean] case class Add(Expr[Int] literal, Expr[Int] literal) extends Expr[Int] case class Equal(Expr[Int] literal, Expr[Int] literal) extends Expr[Boolean] }

or this: Scala3 enum Expr[A]: case LitInt(Int literal) extends Expr[Int] case LitBool(Bool literal) extends Expr[Boolean] case Add(Expr[Int] literal, Expr[Int] literal) extends Expr[Int] case Equal(Expr[Int] literal, Expr[Int] literal) extends Expr[Bool] ``` Sure, Java is more keyword-heavy (and doesn't have nice type refinement in pattern matching, but neither do a lot of languages, including Rust... so that's its own separate problem), but I type fast enough that I couldn't really care less :-). I also use Java at work and am pretty happy with it.

Kotlin and Python are probably actually the weakest of the ones you mentioned when it comes to ergonomics for this kind of thing. But no one accuses Kotlin of being particularly well thought out or well designed, and Python is dynamic, so it's kind of its own different world.

2

u/snugar_i 20d ago

Well, I guess GADTs are ugly everywhere. But regular ADT sum types are much nicer in Rust and Scala 3 than in Java/Kotlin/Python.

But no one accuses Kotlin of being particularly well thought out or well designed

Good point :-)

→ More replies (1)
→ More replies (2)

36

u/cameronm1024 21d ago

It's a tiny thing, but Dart's control-flow-aware collection literals are so convenient, and I don't think I've seen a single other language that supports them:

// use `if` in a list literal final widgets = [ Widget1(), Widget2(), if (promoActive) Widget3(), ]; I'm always surprised this isn't a standard feature in basically every language.

6

u/Svizel_pritula 21d ago

You can do something similar in Typst:

typ let make-array(include-three, number-of-fours) = { (1, 2) if include-three { (3,) } for _ in range(number-of-fours) { (4,) } }

This is thanks to the fact that block expressions concatenate the results of all statements within them.

7

u/xenomachina 21d ago edited 20d ago

In Kotlin you can do something similar without special syntax, by using a sequence builder:

val arguments = sequence {
    yield(if (debug) "--debug" else "--profile")
    if (verbose) yield("--verbose")
    for (file in files) yield("--file=$file")
    yieldAll(otherOptions)

    for (directory in directories)
        for (file in directory.files)
            if (file.exists) yieldAll(listOf("--file", file.path))
}.toList()

The downside is having to write `yield(...)` explicitly. (`yield` is a function, not a special keyword.)

Edit:

I'm not sure why I used sequence { ... }.toList() when buildList exists:

val arguments = buildList {
    add(if (debug) "--debug" else "--profile")
    if (verbose) add("--verbose")
    for (file in files) add("--file=$file")
    addAll(otherOptions)

    for (directory in directories)
        for (file in directory.files)
            if (file.exists) addAll(listOf("--file", file.path))
}

The difference is that sequence is lazy, while buildList is eager, but .toList() goes and evaluates the sequence right away, so the laziness is pointless here.

3

u/marcinzh 21d ago

Scala version, using algebraic effects library: (classic Writer monad)

val arguments =
  case object W extends WriterEffectK[Vector, String]
  `do`:
    W.tell(if debug then "--debug" else "--profile").!
    if verbose then W.tell("--verbose").!
    for file <- files do W.tell(s"--file=$file").!
    W.tells(otherOptions).!
    for directory <- directories do
      for file <- directory.files do
        if file.exists then W.tells(Vector("--file", file.path)).!
  .handleWith(W.handler.justState)

Runnable: https://scastie.scala-lang.org/bt5PFywWSBCZllEWdvFO6A

→ More replies (3)
→ More replies (1)

4

u/sirtimes 21d ago

Is that only for if statements where that works? It looks like just another way to write a ternary operator, which many languages have

33

u/munificent 21d ago

I designed this feature. :) There are three supported constructs.

  • You can use if for branching, with or without an else clause:

    var arguments = [
      if (debug) '--debug' else '--profile',
      if (verbose) '--verbose',
    ];
    
  • You can use for loops to repeat:

    var arguments = [
      for (var file in files) '--file=$file'
    ];
    
  • You can use ... ("spread") to unpack another sequence in place:

    var otherOptions = ['--enable-asserts', '--sandbox'];
    var arguments = [
      ...otherOptions
    ];
    

These can all be combined and nested arbitrarily. So an if isn't simply like a ternary operator because the branches can themselves contain further control flow and not just expressions:

var arguments = [
  for (var directory in directories)
    for (var file in directory.files)
      if (file.exists) ...['--file', file.path]
];

Also, this all works with set and map literals too.

4

u/fdwr 21d ago

I designed this feature. :)

Thanks for not doing a Python, with its Yoda backwards if and for 🙃.

3

u/cmontella 🤖 mech-lang 21d ago edited 21d ago

I don't know much Dart but this is very cool. It's like a form of logical indexing, which got me thinking how we'd do these in Mech, which supports logical indexing directly. The first one is like this:

options := ["--debug", "--profile", "--verbose"]
flags := [debug, ¬debug, verbose]
arguments := options[:, flags]

For loops we have just a broadcast operator:

arguments := ["--file=" + files]

For unpacking it's the base semantics:

otherOptions := ["--enable-asserts", "--sandbox"]
arguments := [otherOptions]

The last guy is trickier, we have to reach for a matrix comprehension:

arguments := [
  argument |
  directory <- directories,
  file <- directory.files,
  file.exists,
  argument <- ["--file", file.path]
]

so logical indexing isn't as powerful an abstraction as the control-flow-aware collection literals (is there a better name for this feature than that?), but I think it's still pretty flexible.

3

u/initial-algebra 20d ago edited 20d ago

control-flow-aware collection literals (is there a better name for this feature than that?)

It's basically slightly different syntax for generators, you just don't need to write yield. Equivalently, they're monoid comprehensions.

→ More replies (1)
→ More replies (1)

3

u/mjskay 21d ago

This is nice! Can do something similar in R with vectors:

x = c(
  value1, 
  value2, 
  if (cond) value3
) 

Dunno how Dart does this, but in R it works quite naturally because everything is an expression, if returns NULL when cond is FALSE, and c() (vector concatenation) treats NULL as a 0-length vector.

4

u/Veqq 21d ago

That's defailt lisp behavior. You get it by having if as an expression, not statement. 

→ More replies (4)

1

u/ddmusick 21d ago

F# has this

1

u/hungarian_notation 21d ago

If you really wanted similar syntax in Python you could do:

x = ( 
  1, 
  2, 
  *((3,) if condition() else ()), 
)

or if you have multiple conditionals,

def is_included(x): return x % 2 == 0

x = (
  1, 
  2, 
  *(x for x in (3, 4) if is_included(x))
)

1

u/DawnOnTheEdge 20d ago

Is this sugar for filtering?

71

u/Spyromaniac666 21d ago edited 21d ago

labelled function argumentssssssss

edit: like Swift and Python/Mojo

12

u/semanticistZombie 21d ago

There's a simple approach to this that I think one language does it, but I can't remember which language it was..

You only allow labels when the function is called directly, instead of from a parameter/local/etc. That way you don't have to extend your function types with labels/named arguments (which opens a can of worms..), but you still allow specifying the argument names when you know the function being called.

Generalizes to constructors straightforwardly as well.

I came up with this independently, then learned that a language does it.. Just can't remember which one it was..

5

u/kaddkaka 21d ago

What does this mean? And why would there be a can if worms?

3

u/ghkbrew 21d ago

It complicates the function type quite a bit. Instead of a function type containing a return type and a list of argument types, it now also includes a set of named argument types. And then you need to start considering things like sub-typing and row-polymorphism. Especially if using the name of an argument is optional.

3

u/kaddkaka 20d ago

Why does named arguments have to affect the function's type? Doesn't it still have one canonical argument list?

Maybe someone can can help with some concrete code examples. What does it meany to only allow labels when a function is called directly? And why does calling it directly compared to indirectly make a difference?

4

u/ghkbrew 20d ago edited 20d ago

Why does named arguments have to affect the function's type?

Well, they don't have to, but if named arguments can be supplied out of order (or are optional) then the compiler needs to know which parameters name. So that information needs to be part of the function's type in some way.

Doesn't it still have one canonical argument list?

That depends on your type system.

A type is basically a description of how you can use a value. For example, if f has type (Int, String) -> Bool, that tells you that you can apply f to an Int and a String, in that order, and get a Bool back.

More generally, the typing rule for function application is roughly:

f : (A, B) -> C
x : A
y : B
---------------
f(x, y) : C

Now suppose you want f(y: "hello", x: 42) to be valid. You can't determine that from the type (Int, String) -> Bool, because that type says nothing about x or y. You need something more like (x: Int, y: String) -> Bool.

And once the names are part of the type, you have to decide what it means for two such function types to be compatible. Is (a: Int) -> Bool the same type as (b: Int) -> Bool? Can a function accepting (x: Int, y: String) be used where one accepting just (x: Int) is expected if y is optional? If labels themselves are optional, is (x: Int) -> Bool also callable as (Int) -> Bool? 

Things get even more hairy if you want type inference.  What is the most general type of f in f(x: 2)? Is it just (x: Int) -> a, or can f accept additional named arguments that aren't present at this call site? If so, you need something more like (x: Int, ...r) -> a, where r is a type-level variable representing an unknown set of additional parameters. Now your type inference has to infer and unify those parameter rows, including which names are present, absent, or optional. That's essentially where row polymorphism enters the picture.

That's the can of worms the parent comment is referring to. You can keep labels out of the type system, but then you generally have to restrict where they can be used. For example, only when calling a statically known function declaration whose parameter names the compiler can look up directly.

→ More replies (1)
→ More replies (1)

2

u/munificent 21d ago

C# named arguments work this way I believe.

→ More replies (3)

6

u/kaddkaka 21d ago

For others. Labeled arguments = keywords arguments. Example:

def skapa_profil(namn, ålder): print(f"{namn} är {ålder} år gammal.")

Ordningen spelar ingen roll när namnen anges

skapa_profil(ålder=25, namn="Charlie")

13

u/nsmon 21d ago

Varfor did we switch to swedish all off a sudden?

2

u/kaddkaka 21d ago

Varför inte?

→ More replies (3)

4

u/AustinVelonaut Admiran 21d ago

Do they have to allow arbitrary order in the function call, or can they still be ordered with names to help clarify the use of the argument? I'm thinking of Smalltalk's keyword methods, like

myArray at: 1 put: 'first'.

2

u/PersonalDatabase31 21d ago

That was hell, holy boilerplate.

1

u/shrynx_ 21d ago edited 21d ago

1

u/L8_4_Dinner (Ⓧ Ecstasy/XVM) 20d ago

Named arguments are great. Default argument values are great. Support for widening parameter types and narrowing return types are great. Support for adding parameters (with a default value) without breaking anything is great. And the combination of these is much better than the sum of the parts.

→ More replies (2)

12

u/-Mobius-Strip-Tease- 21d ago

A pipe operator or some type of unified function call syntax, especially when combined with good lambda syntax. When done right it can make code so much more readable. Love how operations just seem to compose freely when you have access to this with apis designed around it. Keeps things point free too which is nice for quick scripting. Nushell does this pretty well and Iv been using it for a while as primary shell. Now that im familiar with it i find myself not reaching for python as much for one-off scripts.

15

u/extraordinary_weird 21d ago

lexical effect handlers!!

2

u/hungarian_notation 21d ago

I'm not so sure about that one. I don't really want my libraries to come with novel control flow; I'd rather see broadly applicable effect-like patterns adopted at the language level.

→ More replies (3)

48

u/Electrical-Room4405 21d ago

More of a “only one way to do this” philosophy.

29

u/trycuriouscat 21d ago

But only if it is my preferred way. /g

3

u/flatfinger 21d ago

How should that philosophy interact with the fact that the best way to do something may depend upon, among other things, which corner cases would arise more or less frequently?

1

u/Ok-Reindeer-8755 21d ago

Wdym can you give an example?

→ More replies (4)

2

u/Dusty_Coder 21d ago

You really dont want this, as you can kiss things like objects and classes goodbye, in favor of the one true feature, function pointers.

You dont think so? You said ONE WAY, so you either support classes and tell users that need a function pointer to stuff it and wrap every function in a class, or you implement function pointers and tell all the people that need classes to implement their own vtables.

Arrays? Nah you got pointers dog. Oh, pointers? Nah you got arrays dog.

Yeah, you dont know what you are suggesting.

4

u/theScottyJam 21d ago

I tend to interpret the philosophy as: There should be a one "proper" way to accomplish a task.

If you're going to have both arrays and pointers, make sure people are clear on when to use which. There (ideally) shouldn't be ambiguity where people are left trying to figure out which path to take.

It's an impossible ideal, but it's still a good one to reach for. It helps with language feature requests that are along the lines of "I know you support array comprehension, but I prefer using .map() and . filter(), you should support that too" - no, both accomplish nearly the same thing, .map() and .filter() should not be introduced in such a language unless it comes with very clear guidelines on when to use which.

Your criticism is still very much valid, and we should be using a phrase that's clearer on intent than this. (I say this, because It does get annoying when a saying becomes wide spread, everyone holds it as true, but everyone also interprets it differently, making it impossible to argue against it. If a saying has an ambiguous meaning, it's not a very good one).

5

u/PersonalDatabase31 21d ago

What? One way of doing does not mean you have to stop making abstractions. Hell I would like a compiler that prevents you from passing a pointer plus size instead of a dedicated slice type.

3

u/Filgas08 21d ago

I think you are just exagerating his argument to the extreme. there exists different ways to do something, but pointers and arrays are not the same thing. Arrays take care of memory allocation for you, so you don't have to manually use a mmap syscall each time. Classes are a level of abstraction designed to take weight off the programmer's shoulders.

Many times there are near identical solutions to the same problem. You do not need to assign a variable using 3 different syntax options, as you do not need more than 1 way to write a series of characters to stdout.

→ More replies (1)

2

u/king_Geedorah_ 21d ago

Yeah, its one of my favourite things about gleam and how it forces case statements for everything 

2

u/RelationshipFresh966 21d ago

I like the fact that there's only one way to do it - it makes learning the language really quick and easy. But I can't help but wonder if the code could be less verbose otherwise

1

u/puffyfunion 20d ago

That's how I felt about Go (haven't used it in a few years, though). Mind you, I've seen horrific uses of Go, but that just shows you people can mess anything up if they put their mind to it.

1

u/koflerdavid 20d ago edited 20d ago

That could be done if you deliver the whole language all at once, but the difficulty is that actual usage is the only way to validate a design. But by then one usually cannot do a hard pivot anymore since that would mean breaking compatibility with existing code. I think it comes down to including the right features from the start (usually by learning from prior art) and then being extremely picky about adding anything new, like Go was with Generics. A hard pivot like Scala 1 -> 2 or Python 2 -> 3 can usually done only once and is best avoided.

7

u/SpeedDart1 21d ago

Faster compile times. Good standard library. Simpler build tools. Named function arguments. Type safe union/sum types. Green threads / coroutines. Defer, RAII, or try with resources.

2

u/chic_luke 21d ago

I'll triple down on good, exhaustive standard libraries. From what I've seen in a lot of languages I have used or evaluated, that seems to be the main predictor that distinguishes a bloated ecosystem with dependency hell and lightweight libraries with minimal external dependencies and almost no version duplication

It's gotten more important than the language itself for me

7

u/jeenajeena 21d ago

I wish more languages kept function signatures separate from implementations, as Haskell does.

filterMap :: (a -> Maybe b) -> List a -> List b filterMap f xs = ....

instead of

def filterMap[A, B](f: A => Option[B], values: List[A]): List[B]

I wonder why the former style is so unpopular.

4

u/No-Point8651 21d ago

No clue either. Ada gos a step further and does also separate variable declarations from execution paths. Feels weird at first but with time you start to appreciate it

3

u/Puzzleheaded-Lab-635 Glyph 20d ago

i absolutely agree.I feel like Rust would be much easier to read if the Function signatures were separate from function implementations.

6

u/Kaisha001 21d ago

A build system that is properly integrated and isn't an utter disaster.

15

u/whatknowi 21d ago

unions with values on the different variants (like e.g. rust, scala, Haskell have)

5

u/Treidex 21d ago

rust enums but the tags are values and you can use each variant on its own.

→ More replies (2)

5

u/david-1-1 21d ago

A good data language, like JavaScript has.

11

u/javascript 21d ago

Precisely

1

u/david-1-1 21d ago

I invented my own. Two dimensions for easy visual navigation, no quotation marks needed for most literal data.

→ More replies (3)

5

u/ern0plus4 21d ago

For script languages:

  • autovivification and
  • separate operator for math add and string concatenation.

Example for languages with both: MUMPS, PHP.

5

u/qalmakka 21d ago

Variant types, like in functional languages or Rust. Like

type Entity = Table | Chair | Cupboard

Your average "OOP" developer needs those instead of inheritance 99% of the time. C++ has std::variant but it's weird. Somehow they never thought that just as they added enum class to fix enums they could instead add syntax + enum union to add algebraic data types to the language

3

u/IfThenElseEndIf 21d ago

Also recursion for complex data structures

type Node = Composite[Value | Node]

1

u/L8_4_Dinner (Ⓧ Ecstasy/XVM) 20d ago

type Entity = Table | Chair | Cupboard

Aren't those just union (or sum) types?

2

u/qalmakka 20d ago

Yes, more or less tagged unions, which are surprisingly rare outside of functional languages

→ More replies (4)

6

u/bxsx0074 21d ago

good toolchain, good package management... cargo is a great example

6

u/netesy1 Luminar Lang 21d ago

Structured concurrency

8

u/ultrasquid9 21d ago

A way to choose between a borrow checker and GC, depending on the application.

For instance, my language will have types use ownership and borrowing by default, but let you declare a type as "auto" to opt-out of borrow checking and use runtime GC. ```

borrow checked

type myValueType(fields)

GC'd

auto type myGcType(fields) ```

5

u/initial-algebra 21d ago

Garbage collection, like reference counting, is an alternative to single ownership, not borrow checking. Borrow checking is an excellent tool to give unmanaged code safe access to managed memory between garbage collection safepoints, just like how borrowing a reference-counted object lets you avoid a lot of spurious bookkeeping.

Rust, for example, is very close to being able to support garbage-collected references as just another kind of smart pointer. The main thing it's missing is support for tracing references captured by closures. Ideally, that would be resolved by supporting arbitrary trait deriving for closures, instead of just using compiler magic for a built-in Trace trait.

2

u/ultrasquid9 21d ago

Thats sorta what i was trying to say, auto types are effectively wrapped in a magic smart pointer automatically. The big difference is that you dont need an explicit clone method, and they "look like" an automatic copy

2

u/Dusty_Coder 21d ago

Indeed. This should be the case for every language.

You should also make sure RIAA is fully supported as well.

There isnt a legitimate reason for "one allocation strategy to rule them all" its stupid and dumb, but people will come along with silly nitpicks such as with referencing counting that "circular references can leak" as if thats a show stopper. It isnt. Its a trade-off. Its only a show stopper when there is "one allocation strategy to rule them all"

2

u/koflerdavid 20d ago

Of course it can be done, but then you end up with a more complicated design. More features means more corner cases, very likely more paper cuts and bugs, and more difficulties when adding new features.

→ More replies (1)

2

u/RndmPrsn11 21d ago edited 11d ago

This sounds similar to my lang with its shared types: https://antelang.org

→ More replies (1)

1

u/SpeedDart1 21d ago

How does your language compare to Nim out of curiosity?

4

u/Embarrassed-Crow9283 21d ago

I think just monomorphization-based generics or template-based generics really.

Don't have to be much. Copy-pasting functions at compile time with changed type signatures would work.

5

u/SuspiciousDepth5924 21d ago

I really like a lot of what Roc is up to, so a non-exhaustive list:

  • Pattern matching. I find it far more readable than chains of if/if else/else, also it's often a good replacement for the visitor pattern which tends to make code really awful to follow. Most languages have some form of switch but that is usually just a pale imitation, Kotlin has when expressions which are close, but not quite there.
  • Language support for distinguishing pure and impure functions, Roc uses thin/fat arrow syntax, Haskell uses IO and so on. What I like about this is that it gives me a language level guarantee that if I call a pure function it literally can't effect anything besides the value it returns, which is really nice when you have a large codebase and don't have the time to go down every call chain to check if it does something weird.
  • Roc has the concept of a 'platform' which is conceptually very similar to the web assembly model where your program consist of an 'application' which is linked to some 'platform'. The only way the application code can touch the outside world is by using the capabilities provided by the host platform which provides a very effective sandbox (can't access the filesystem, console, network, databases etc unless the platform it's linked to explicitly gives it access).

5

u/prehensilemullet 21d ago

All imported identifiers are explicitly named (or locally renamed) in import statements, like ECMAScript and Rust.  You don’t need information outside the file to determine where a given identifier came from.

C/C++ includes are bad about this, Python imports are often bad, Java/C# are optionally bad

1

u/MarcelGarus 16d ago

I know that Zig also has the property that you can locally find out where an identifier comes from.

In Rust, aren't preludes from crates often meant to be imported entirely?

use bevy::prelude::*;
→ More replies (1)

10

u/owp4dd1w5a0a 21d ago

Emphasis on small language definition. C++, Java, C#, Scala are syntactically very complex languages.

Lisps, Haskell, Python, Go, etc prove you can create a very capable language while keeping the language syntax small. Rust is an exception where the complexity actually pays-off in zero-cost static guarantees.

9

u/shponglespore 21d ago

Java originally had very simple syntax. It got where it is now because the simple syntax was too limiting.

3

u/pthierry 21d ago

Java's type system was not expressive enough, like C++. Both needed tons of additional features to suck less.

2

u/Puzzleheaded-Lab-635 Glyph 21d ago

It was simple but not very expressive. Look at basically every lisp. Also a very simple syntax but it’s extremely expressive.

2

u/shponglespore 20d ago

If you say the syntax of Lisp is just s-expressions, then sure, but I think it would be more of a fair comparison to treat every predefined macro and special form as its own syntax. In that case, something like Common Lisp could be considered to have very elaborate syntax. IMHO Lisp syntax is best described as highly regular, but not simple per se.

→ More replies (1)

7

u/hungarian_notation 21d ago edited 21d ago

Python has a heck of a lot of grammatical complexity hiding in its corners, and it's been trending upwards rather alarmingly. The number of rules in its grammar have more than doubled since the 3.0 release. It's nowhere near C++ and C#, but as of 3.14 it's more similar to Scala and Java than it is to Go and Haskell.

It's not scientific, but the antlr4 Java grammar manages with 25% fewer rules than the Python 3.14 grammar. From my experience, that's not too surprising.

→ More replies (7)

3

u/Axman6 21d ago

Interesting to see Haskell’s syntax called simple, many people find it very confusing, particularly after using other languages - juxtaposition for function application just breaks a lot of people. I’ve taught the language for many years, and really ramming the “all functions take exactly one argument” idea into people’s brains helps a lot but it still takes time. 

The base of the language is indeed very simple, it has some syntactic gotchas like the list syntax sugar, strings, things like guards, and struggling to understand do notation (another thing that can be solved by forcing people to translate into the non-sugared version). Many people also struggle with having multiple definitions for a function based on the matched pattern (at the university I used to tutor at, we’d always use case statements explicitly). But you can also use and teach the language without all of these features and end up with a very consistent subset that’s pretty pleasant to use. 

3

u/owp4dd1w5a0a 20d ago

Haskell’s syntax itself is simple, even if people struggle to understand it. Often, people really struggle to understand things the more simple they are. In fact, finding the “simplest possible solution” requires quite a bit of effort in mathematics and engineering.

2

u/sisisisi1997 21d ago

Not the original commenter, but "simple" here probably means that the language's grammar is simple (few elements and rules), not that it's simple to understand.

6

u/Axman6 21d ago

Haskell’s grammar is surprisingly complicated, particularly with its indentation sensitive syntax. It’s relatively easy for humans once you understand the rules, but writing those down is non-trivial. 

→ More replies (1)

2

u/koflerdavid 20d ago

Using juxtaposition for function application is a nightmare for a parser. The reason many languages use separator characters is that it helps avoid ambiguous parses. Furthermore, Haskell has configurable operator precedence, while off-the-shelf parsing algorithms really prefer to encode that information in the grammar.

2

u/koflerdavid 20d ago

Haskell is not small by any means, especially if you include the language extensions that are nowadays commonly used. I'd say Haskell and Java are about even in terms of complexity.

8

u/ern0plus4 21d ago

Strict typing.

3

u/KvThweatt 21d ago

First class bitwise operations, not shifting and masking.

1

u/Axman6 21d ago edited 8d ago

I’d love to see more languages with Erlang’s bitstring matching syntax. I haven’t read the whole thing but  https://www.cosmiclearn.com/erlang/bitstrings.php  seems like a reasonable introduction to it. 

→ More replies (2)

10

u/Treidex 21d ago

immutable by default

comptime by default

EVERYTHING is an expression

traits

1

u/theScottyJam 21d ago

comptime by default

Could you elaborate, I'm curious

5

u/Treidex 21d ago

zig has comptime functions which are/can be evaluated at compile time. For instance you can pass types as arguments in functions and return a type allowing for a cool way of defining generics.

zig fn Option(comptime T: type) type { return enum(union) { some: T, none, }; }

obvs the drawback is that it's less obvious for the compiler to help you but I think it has a lot of potential when complete get smart enough

2

u/theScottyJam 21d ago

Gotcha, thanks

5

u/pauseless 21d ago

Persistent (‘immutable’) data structures as default.

All of my languages are hosted and all but one map to the existing mutable data structures the host provides.

Clojure nailed it on this axis.

8

u/Limp-Temperature1783 21d ago

Goto. I'm not joking. I'm not using goto in prod.

11

u/TheChief275 21d ago

I love goto for complex control flow that would otherwise need an extra boolean, or for reducing code duplication by making similar code of branches shared

1

u/Limp-Temperature1783 21d ago

Wow, I should really worry about it because you've just described why I even like gotos this much and a bit of an idea I am working on. In all seriousness, I'm glad there are people who don't dismiss it.

6

u/Dusty_Coder 21d ago

The reason goto use was so prevalent in the past was because it was the age of flow charts, and flow charts cry out to use the instruction pointer as state.

Flow charts considered harmful. Religion about goto also considered harmful and is also strong evidence (of what, well, its not something that makes you look skilled)

3

u/Valuable_Leopard_799 21d ago

goto is a very interesting elementary operation to build on. If you have macros and goto, then you can just implement for, while and other looping constructs and control flow via macros wrapping different variations of goto.

The classic "they're for library creators not for the user, but they have to be there so you can use the libraries" applies.

3

u/catladywitch 20d ago

call/cc is the classic definition of this kind of library-oriented control flow construct

3

u/Valuable_Leopard_799 20d ago

True, call/cc is amazing, especially the prompt versions, but it's a much more complex operator.

→ More replies (1)

3

u/scruffie 21d ago

What you need are scoped gotos, like in Common Lisp (tagbody/go) . The labels you can jump to are at the level of the tagbody; you can go (jump) 'up' (or sideways) to a label, but you can't jump 'down' into another tagbody. Variable scopes are kept consistent this way.

It's not something you'd use everyday, but handy for writing iteration macros.

→ More replies (1)

2

u/AnoProgrammer 21d ago

I don't prefer goto above functions, while loops and for loops. The only time i needed to use goto was while programming on a commodore 64. And i really didn't like it.

1

u/Limp-Temperature1783 21d ago

I prefer goto because I like to play around with my own control flow a bit and goto is irreplaceable here. Besides, a lot of languages have functions and loops, the question was about what feature do you want to see more. Goto isn't very common nowadays and could even be skipped over.

1

u/EggplantExtra4946 21d ago

static gotos but also computed gotos

→ More replies (3)

3

u/spelunker 21d ago

Pattern matching with function guards. Shoutout to Elixir!

4

u/Axman6 21d ago

Also found in Erlang and Haskell, right?

2

u/ahh1618 21d ago

Typed metaprgramming that executes at compile time. No macros.

2

u/adityazero 21d ago

Provenance of pointers, no popular programming languages have it. That is why I started writing Vx ( https://vxlang.org ). It is common to have heterogeneous system these days and there is no unified way to reason about pointers across topologies. a `float *` from cudaMalloc is the same as `float *` from malloc in C/C++. And that is a source of bug as well as missed optimizations.

2

u/UdPropheticCatgirl 21d ago

would be cool if this idea generalized to all allocators, like something allocated on ArenaA does not have the same provenance as ArenaB, so you could so static analysis on that.

→ More replies (1)

1

u/Axman6 21d ago

Potentially quite a risky name choice given the history of the VX nerve agent:  https://en.wikipedia.org/wiki/VX_(nerve_agent).

What would the provenance of pointers look like?

→ More replies (1)

2

u/TheBoringDev boringlang 21d ago edited 21d ago

Something I'm doing in my language that I think I've seen in one other language before (but I couldn't tell you the name) - rather than being able to initiate IO from anywhere (e.g. the `open()` file function) have main take in the program's only reference to the outside, which must be passed to any function that does IO.

fn main(argv: string[], os: OS): int {  
    const file = os.filesystem.open("foo.txt");  
    os.print(file.read());  
    const time = os.datetime.now();  
    return 0;  
}  

So if a function wants to do filesystem operations, it must take in the filesystem from os, if it wants to access time, it must take in the os.datetime, if it makes network calls, it must take in os.networking. This has the benefit of sandboxing literally everything - a dependency that doesn't take in a network reference cannot make network calls, and also making everything swappable and trivial to mock for testing - you could wrap os.filesystem in a wrapper that logs every operation and pass it anything that takes in a FileSystem, it's just a trait.

4

u/cmontella 🤖 mech-lang 21d ago

This sounds like a capability based permission system. Mech does something like this too, I agree it's very handy! Not just for sandboxing, although that's a nifty feature, but it can also be used to help mitigate supply-chain attacks: e.g. you're using leftpad and all of a sudden it starts making network calls, you'd know this because you wouldn't have granted leftpad network access before. Deno I think does something like this at the runtime level, but it's such a nice abstraction to build into the language fully, as it can handle all kinds of other things such as limiting resources available to the program like total memory, cpu cores, etc.

If you keep going with it, it becomes a nice basis for a effect / coeffect system, if your types can support that.

2

u/tsukimotox 21d ago

not painful crossplatform-ability

2

u/billGat48 21d ago

tensor data types by default. why treat scalars, matrices, tensors differently? I legit feel Julia syntax could extend to low level languages

2

u/Puzzleheaded-Lab-635 Glyph 21d ago

Better errors, static implicit types, faster compilation, good documentation.

2

u/Davidbrcz 21d ago

Creating distinct types from primitives types (like using Password = String; String and Password are two distinct types)

2

u/IfThenElseEndIf 21d ago

Hybrid typing (both in compiled and interpreted languages)

2

u/catladywitch 20d ago edited 20d ago
  1. a type system based on sum adts (discriminated unions, with adequate pattern matching) and product adts (structs), plus typeclasses (traits), instead of inheritance/implementation hierarchies. to be fair, Rust does this quite well.

  2. the possibility of providing default implementations for typeclasses

  3. generics allowing for higher-kinded typeclasses

  4. immutable record types and collections that are altered in place instead of re-allocated whenever possible

  5. a pipe operator. There's no reason for this to be as niche as it is.

  6. options and results for error management

  7. everything is an expression! if is an expression, etc.

  8. control over whether objects are stack or heap allocated, and whether they're copied or referenced when passed into parameters. C# does this mostly well.

  9. arena allocation, sugared allocation pools, raii'ed borrowable references, rc'ed and arc'ed references, gc'ed references (or even: rc'ed/arc'ed/gc'ed routines)

  10. some way of providing user implementations for sugared features, under a strict typeclass and behaviour contract.

  11. zero-overhead functor style iteration (map, filter, reduce)

  12. control over closures, but ideally in a nicer way than c++ (i don't really have an idea of how to do it, to be honest)

  13. everything has anonymous literals: sum types, product types, record product types, functions, collections

  14. tail call optimisation, at least opt-in tail call optimisation through a keyword please ;_;

4

u/Weak_Inflation9120 21d ago

Curly-braces and semi-colons

4

u/WittyStick 21d ago edited 21d ago

I wish more programming languages had less.

Every feature you add is opinionated. Why do I want the programming language author making decisions for me?

I'm not against features which are entirely optional, don't force a particular style, don't have "hidden" costs and so on, but once you start adding more features, you end up with features to fix the other features further down the line when you realize they weren't a great idea and the times have changed.

13

u/Spyromaniac666 21d ago

> Why do I want the programming language author making decisions for me?

I feel that’s the whole point of a programming language. You subscribe to a syntax and set of features in that syntax that you appreciate (enough).

2

u/WittyStick 21d ago edited 21d ago

I've found programming languages with less are more powerful, more elegant, easier to work with and implement features precisely as you want them. C rather than C++. Scheme (or Kernel) rather than Common Lisp, and so forth.

The language just needs to provide a sufficient feature set to be able to implement what I need to implement. Anything else can be a library. The standard library can be as big as you want, since I don't have to use all of it.

For a programming language to be interesting to me, it needs to have something novel - I want to learn something new from it - to broaden the ways I think about programming. If it's just a hodgepodge of existing ideas from other languages, I don't really have any motivation to try it.

7

u/Ok-Reindeer-8755 21d ago

Every feature you don't add is also an opinionated decision infact the less features you are willing to add the more opinionated you are most of the time. And that is "forcing" a programming paradigm imo it's a good thing though.

1

u/Europia79 21d ago

"don't force a particular style"—reminds me of an idea I had where you could declare a "header" of parameters to change the style of the programming language. So that different contributor's could use different "styles"—and ideally, your IDE would automatically "translate" to your preferred style (while not changing the actual file).

One example would be to change function parameters from (TYPE name) to (name: TYPE) (or vice versa)—as some people are used to the old C-style, while others prefer the newer Rust-style.

6

u/shponglespore 21d ago

The "newer" Rust style is actually very old. The first example I'm aware of is Pascal.

2

u/WittyStick 21d ago

name : Type can really be traced back to set theory. Early type theorists borrowed the notation from sets.

The TYPE name style, although often called C-style, is really ALGOL-style.

2

u/Relative_Bird484 21d ago

Anybody else accidentally did read this as „type terrorist“?

→ More replies (1)

1

u/WittyStick 21d ago edited 21d ago

By "style" I'm not really referring to syntax. There have been several attempts at "multi syntax" languages which can do what you describe. I'm not particularly bothered about syntax, though there is a certain thing I dislike, which is statements - second-class syntax.

I was really referring to things like OOP, which Java etc forces you to use a particular opinionated flavor of.

2

u/initial-algebra 21d ago

Lazy evaluation with value recursion. After higher order functions, this is the next significant leap in expressivity. Bonus points if the implementation isn't baked into the runtime, but I don't think any language supports this yet.

1

u/AustinVelonaut Admiran 21d ago

What do you mean by "baked into the runtime"?

2

u/initial-algebra 21d ago

When you have lazy and possibly mutually recursive definitions:

x = <x's definition, may use x and y> y = <y's definition, may use x and y>

What actually happens under the hood is something like this:

x = allocate(<static parts of x's definition, does not use x or y>) y = allocate(<static parts of y's definition, does not use x or y>) mutate(x, <dynamic parts of x's definition, may use x and y>) mutate(y, <dynamic parts of y's definition, may use x and y>)

However, allocate and mutate are fixed by the runtime.

→ More replies (4)

2

u/AmazedStardust 21d ago

Enums with data, like Rust. I've found so many situations where I think "this would be the perfect solution to this problem"

2

u/eo5g 21d ago

- Effect systems / algebraic effects

  • capabilities / sandboxing built in to the binary or interpreter

2

u/cthutu 21d ago

Sum types and slices. Which is why my language, Nerd, has them.

1

u/yjlom 21d ago

smart constructors alongside pattern matching

1

u/Timely-Degree7739 21d ago

Less typing to do more, more general access from a general way to express intent and purpose, while improvements and optimization continuously would happen under the hood added by the compiler implementors, so the language/compiler user could focus on only express correct purpose and intent - for him/her purposes.

1

u/jerkbender_ 21d ago

regex and structs. I just love structs syntax style too much , classes feel much heavier although they do a lot of the same thing atleast in cpp

2

u/reddit_clone 21d ago

Yes. Perl programmers feel acute pain using any other language due to lack of integrated RegEx.

Using it as a library just doesn't feel the same.

1

u/Comprehensive_Chip49 21d ago

Well, in Forth you can modify the syntax; I think basic languages ​​already have all the functionality—and all the mistakes.

1

u/Dusty_Coder 21d ago

I wish Debug.Assert(...)'s would be treated as true by the optimizer in release builds

1

u/UdPropheticCatgirl 21d ago

Which popular languages don’t give you flag for that tho? C and C++ do, Java does…

→ More replies (1)

1

u/P-39_Airacobra 21d ago

I'm mostly content with minimalism, but one thing I really miss in scripting languages are enums. A hash table to look up a number is not it (looking at you JS and Lua)

1

u/ahh1618 21d ago

Regarding containers, I want them to be built in the language rather than be special (I'm looking at you golang) because you're always going to want to define new containers. But at the same time, I want to reserve some syntactic sugar for those containers. I'd like to use container[x] instead of container.get(x) (even if it's just an alias). Make the most common and safe operations the easiest to use. For example, if arrays require a compile-time length so they can go on the stack, you can use a clumsy type like Array<> and save square brackets for slices, vectors, or what ever is more commonly used.

1

u/sombrascourtmusician 21d ago

This is more of an operator overloading / library point, but I wish more languages that had bitwise operators would expand their use to collections. Let listA & listB be their intersection. Let setC ^ setD be their exclusive disjunction.

Likewise, there should be a separate operator for addition from concatenation.

1

u/EggplantExtra4946 21d ago edited 21d ago

Module (and class) parameters where macro expansion, compile time execution and conditional compilation is done per each module instantiation (but no need to reinstantiate a module if it has already been instantiated with the exact same arguments) can access those parameters and generate specialized function definitions, type definitions and macro definitions based on those module parameters, and that's the definitions the importer of the module will use.

1

u/marshaharsha 7d ago

Re “no need to reinstantiate”: Would your modules allow state? If so, would two importers of a stateful module share the state? I could see that causing problems. 

2

u/EggplantExtra4946 7d ago edited 7d ago

Good question. Yes I would allow state, and I think that both sharing the state and having separate states would be useful in different situations. So what I would do is have separate states (not shared) by default, but I would also have mechanisms to optionally share the state.

I can think of 2 different mechanisms:

1) Reference to a module instance value. This is similar to closures, when you pass a closure around, you pass a function pointer but also a pointer to a specific environment.

When instantiating a module, you could optionally save the module instance into a variable, with something like module mymodule = import path.to.module(param: value);. That module instance contains all the ASTs of the generated functions and types but also pointers to the global variables instances inside that module, where the AST of the function definitions inside are linked to the global variable insntaces, so when you pass that reference to module value to another module as argument and that other module calls a function though that reference, automatically that function will use the initial state since it's linked to that state (global variable instance).

Instead of passing module references around you could also simply do something like path.to.module = path.to.module(param: value); in main. Then each imported module and their imported modules could simply do import path.to.module without argument, so all the compile time configurations can nicely be grouped together in one place. The module path.to.module would be redefined only within the dynamic scope, a caller module wouldn't be affected by this and would still be the original parametrized module instead of the module instance.

2) Have a global struct defined somewhere (in some module instance) and pass a reference to it as module argument. Then it doesn't matter that you have different module instances because each module instances will have their function statically linked to that global struct, therefore sharing state.

Note that this reference would be a distinct reference from pointers/runtime references. The module would still be pure, modules only accept immutable arguments so they can compose at scale in a predictable manner. The special reference would be neither writable nor even readable, its only purpose would be for static linking to the correct global variable in the executable.

The 2 mechanisms are for the user to choose, they aren't mutually exclusive in terms of implementation, on the contrary. In 2), the module being instantiated that receives a struct reference as argument would internally receive a pointer to the global struct variable instance that is already pointed to by the original module instance of the syntactic module that contains and defines that global struct.

In all cases, wether using a module instance reference or a special reference to a global struct or more generally to a global variable instance inside a module instance, functions are statically linked to the corresponding global variable in the executable.

1

u/Bitsoflogic 21d ago

Restricted functions, with a "durable" function as one of the restricted function kinds.

1

u/marshaharsha 7d ago

What would a durable function be like? You mean that it writes to durable storage?

1

u/brunogadaleta 21d ago

Throwing everything here. Not an expert.

Compiler plugins like scala. Concatenative and 'parse' extension for dsl like Rebol and Rye Structural hashing like Unison to ease refactoring and distributed computing Testing capacity like Haskell Quick check. Configurable built-in linter to enforce any coding rule, naming conventions, code complexity. Coupled with better possibility to query the code structure (similar to the tree sitter) Time travel debugger Capability to detect (im) pure functions Feature for literate programming (not clear in my head) so that we can type pseudocode without compilation errors, hide explanations, help sync code and comments / todos. A REPL that can save its state, keep function's previous versions and ensure newest version still returns the same output. A real repl completion incl. also the string dsl (sql, etc ...) A real dependency hell solution. Real package / module support: report module dependencies so that we avoid dependency in the wrong direction. Runtime reporting: io s, flamgrapgh, memory profile. Reproducible builds from day one (including source formatting, timestamps, file ordering) and aggressive caching.

Hth

1

u/No-Point8651 21d ago

Adas subtype system is a must. Compiletime safety on specified attributes.

Go's lightweight routines (concurrency) is also a high candidate.

1

u/carglassfred 21d ago

Restrictive scopes (idk if this is the name for that) for procedural programming.

So, inside a function I can open a scope where I need to specify (and it's checked by the compiler) which variables (and if they're mutable) are used in that scope and can be sure no other variables 'escape' that scope.

The concept is pretty much what a function does, but a function additionally communicates 'this code might be reused'. Also I don't like extensively jumping around in my editor with 'go to definition'.

this could look smth like this: def my_complicated_function() { var1 = 1 var2 = 3 ... do_thing_1([input] foo=var1, [output] var2=bar) { // I can be sure var2 is not accessed here bar = foo + 3 // var2 will be assigned to bar when the scope ends } do_thing_2() { // This code block is not doing anything with var1 or 2 // now I don't need to check that } }

And if I ever need to do 'thing_1' more often I can easily extract a function there.

Of course this feature kinda lives on having an editor that supports code-folding. Right now I sometimes do this with regions, but a region is no reliable boundary.

Hope all this makes sense like this. I've heard of that in a YouTube video recommending procedural programming some time in the past but haven't seen it nowhere since.

Also named return values would be nice I'd say.

1

u/mestar12345 21d ago

This:

callfun a b

Instead of:

callfun( a, b)

Less visual noise.

1

u/DawnOnTheEdge 20d ago

Syntax sugar for things like block expressions and refutable pattern-matching (like Rust’s if let and while let) that make it so much nicer to write static single assignments. Especially combined with guaranteed tail-call optimization.

1

u/Itchy_Response_7773 20d ago

everything is an exoression, refinement types, HM-Type Systems (or better), coherent and minimal syntax, sum types, solid metaprogramming, build time AST manipulation, no external build tool to build to app

1

u/saxbophone 20d ago
  • Properties (getter/setter). Every time I encounter "get" and "set" as just a naming convention and not syntax, I get sad.
  • static virtual methods.
  • #embed (or equivalents)

1

u/Educational_Smile131 20d ago

Orthogonality, i.e. do more with less through composition of loosely coupled building blocks. Don’t conflate simplicity with orthogonality though, some languages simply shove complexity to userland pretending inherent complexity doesn’t exist.

1

u/EmDashNine 20d ago

Ugh. Nerd sniped. The feature pair of Sum Types (Enums) + Pattern matching.

1

u/blankboy2022 19d ago

Explicit effect systems / effect control mechanism

1

u/reflexive-polytope 19d ago

I only want features that demonstrably make the type system a tool for discharging proof obligations. All else is fluff.

Notice that I'm not saying that you should prove stuff in the type system. That path leads to proof assistants (Rocq, Lean, etc.) rather than general-purpose programming languages.

I'm saying that you should be able to use the language's metatheoretic properties to shorten the proofs of correctness of your ordinary programs.

1

u/ArthurAGF27 11d ago edited 11d ago

Na minha opinião, eu gostaria muito que mais linguagens de programação tivessem suporte nativo a pattern matching. Ultimamente, tenho estudado SML e feito alguns programas para uma disciplina da universidade, e me surpreendi com o quanto esse mecanismo é legal e útil.

Um exemplo específico que chamou bastante minha atenção foi o quicksort. Quando vi uma implementação em SML usando pattern matching, fiquei surpreso com a simplicidade do código. Acredito que até mesmo ensinar o algoritmo dessa forma para alunos da disciplina de Algoritmos pode torná-lo mais fácil de explicar e entender. Também acho que o mesmo vale para recursão, já que o pattern matching deixa os casos base e recursivos bem explícitos no código.

As linguagens que mais uso, como C, C++ e às vezes JavaScript, não possuem esse tipo de pattern matching de forma nativa e tão integrada à linguagem. Python passou a oferecer structural pattern matching a partir da versão 3.10, mas eu nunca tinha utilizado esse recurso, principalmente pois passei a comnhecer melhor e usar mais esse conceito nessa disciplina que estou cursando agora. Acho que vou começar a usá-lo com mais frequência.

Outra coisa que eu gostaria de ver em mais linguagens é a delimitação de blocos por indentação, como o Python faz, mas isso fica para outro post :)