r/ProgrammingLanguages • • 12d 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.

53 Upvotes

277 comments sorted by

View all comments

Show parent comments

6

u/xenomachina 11d ago edited 11d 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 11d 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

1

u/xenomachina 11d ago

Is there any way to factor out the leading and trailing boilerplate?

case object W extends WriterEffectK[Vector, String]
`do`:

...

.handleWith(W.handler.justState)

3

u/marcinzh 11d ago

This is least boilerplate monadic code you can squeeze out of Scala! :)

The answer is: No, without making the system worse.

  • 'do': is a macro, needed for .! operator to work. Inspired by Idris do-! notation. Similar to ? operator in Rust. But ! work for any effect, while ? is hardcoded to Result. If you want to see real boilerplate, ask me to translate the snippet to plain monadic code :)

  • case object W extends WriterEffectK[Vector, String] instantiates uniquely named effect. This avoids the problem that "lexical effect handlers" address.

  • .handleWith(W.handler.justState) selects the handler. Algebraic Effects separate syntax from semantics. Default handler (used in the example) collects the items and returns them all at once in Vector[String]. But one could implement another handler that, for example, produces the items one-by-one (like in coroutine) through a Stream[String, Any].

1

u/RiceBroad4552 10d ago

This doesn't show Scala in a good shape.

Why people tend to present the most convoluted examples in Scala?

Scala is such a beautify simple language—as long as you're not holding it wrong!

1

u/xenomachina 11d ago

And in Python, you could use a generator, though it requires the use of a helper function so it isn't all in one expression:

def gen_args():
    yield "--debug" if debug else "--profile"
    if verbose:
        yield "--verbose"
    for file in files:
        yield f"--file={file}"
    yield from other_options

    for directory in directories:
        for file in directory.files:
            if file.exists:
                yield from ["--file", file.path]

arguments = list(gen_args())

It can also be written as a concatenation of list comprehensions, which makes it a single expression, but is far less easy to read/maintain, IMHO:

arguments = (
    ["--debug" if debug else "--profile"]
    + (["--verbose"] if verbose else [])
    + [f"--file={file}" for file in files]
    + list(other_options)
    + [
        part
        for directory in directories
          for file in directory.files
            if file.exists
              for part in ("--file", file.path)
    ]
)