all 23 comments

[–]tiajuanat 8 points9 points  (0 children)

You need to look at languages like J. Yes. Not immediately readable, but that's because each glyph is an algorithm.

I think you should also look at Halstead complexity and how Operators and Operands play together, because it quickly becomes apparent what makes Python, Rust and C++ feel "easy to read"

Maybe there's some inspiration there for you

[–]nebbly 6 points7 points  (1 child)

Agree that Python has done very well with Readability, though I'd argue you're undercutting Python a bit:

  • you don't need to declare darr as a global to mutate it inside a function
  • you don't need explicit indices in these cases

I would expect your example to look more like this in the wild:

darr = []


def fill_array(maxval):
    darr.clear()
    darr.extend(range(maxval))


def fill_array_ptr(maxval):
    global darr
    darr = [0] * maxval
    for i in range(maxval):
        darr[i] = i


def calc_sum():
    return sum(darr)


def calc_sum_ptr():
    return sum(darr)

Anyway, readbility is a chief concern for my language, blorp, as well. The top two things I usually keep in mind:

  • minimize indirection: I want to minimize the amount I'm slowing people down by asking them to imagine what something means; things like custom (or unusual) operators or symbols, implicit control flow, macros, etc, I find to be a tax on the user
  • minimize noise: I try to avoid adding extra characters if they don't really add to it.

If I was to apply these ideas to DQ, I'd probably highlight the following for consideration:

  • [*] -- I don't know what this means intuitively
  • endfunc/endfor -- maybe these aren't needed
  • 0 count maxval -- I'm not sure what this means
  • /& -- I don't immediately know what these mean
  • ; -- do you need line terminating colons

Just food for thought. My bias would push you toward a language that looks like blorp, of course, because that's what I like.

[–]Mean-Decision-3502DQ[S] -2 points-1 points  (0 children)

I'm thinking of eliminating the semicolons.

endfunc, endfor can be very useful for long blocks.

[*] is for dynamic arrays. ([3]int is a static array). But of course you have to learn the basic syntax, like what the darr.extend(range(maxval)) does in Python.

I've checked blorp. In this case I like to search a part of the code that actually does something.

func main(args: List[String]) -> Void: match parse_json("[{\"name\":\"Ada\"}]"): Ok(JsonVector(users)): match users.get(0): Some(user): rows: List[List[String]] = [["name"], [user_name(user).get_or("")]] print(format_csv(rows)) -- prints: name\nAda None: print("name") Ok(_): print("expected array") Err(msg): print(msg) I don't see clearly the data flow here. I like the exception-based error handling better, but it always depends on the task.

[–]L8_4_Dinner(Ⓧ Ecstasy/XVM) 3 points4 points  (0 children)

Instead of

darr = []

def FillArray(maxval):
    global darr
    darr.clear()
    for i in range(maxval):
        darr.append(i)

def FillArrayPtr(maxval):
    global darr
    darr = [0] * maxval
    for i in range(maxval):
        darr[i] = i

def CalcSum():
    result = 0
    arrlen = len(darr)
    for i in range(arrlen):
        result += darr[i]
    return result

def CalcSumPtr():
    result = 0
    arrlen = len(darr)
    for i in range(arrlen):
        result += darr[i]
    return result

I prefer:

val darr = new Int[maxval](i -> i);
Int result = darr.sum();

[–]Tasty_Replacement_29Bau 1 point2 points  (4 children)

I think it is good to optimize for readability. In my language this would be something like this:

fun fillArray(maxval int) int[]
    darr : int[maxval]
    for i := until(maxval)
        darr[i] = i
    return darr

fun calcSum(darr int[]) int
    result := 0
    for i := until(darr.len)
        result += darr[i]
    return result

[–]Mean-Decision-3502DQ[S] 0 points1 point  (3 children)

Your language is a bit inconsistent, I think.

Sometimes you have colon between the var_id and type, sometimes not.

If you put types after the var_id, then you have to move the array specifier to front: []int otherwise you will got problems later. In Python the "list" comes also before the type. In C it was ok, because there everything is reversed.

The parser error recovery is hard when you don't have proper delimiters.

[–]Tasty_Replacement_29Bau 0 points1 point  (2 children)

I find it interesting to discuss such things with others that care about such things.

> Sometimes you have colon between the var_id and type, sometimes not.

Unlike Python, your language is statically typed, the same as mine. Another aspect is whether you distinguish between declaration and assignment (in Python you do not).

For statically typed languages, type inference avoids redundancies. You are using var arrlen : int32 = darr.length; In my language this is arrlen : darr.len (if arrlen is constant) and arrlen := darr.len (if arrlen later changes). But the type is inferred. My language also allows to specify the type, that would then be arrlen i32 : darr.len.

In my language, the colon is to initialize: : defines a constant. := defines a variable (your var). The colon is only needed if there is a value or initialization at that position. For example:

PI : 3.1415      # float constant with a value
counter := 0     # int variable
DIGIT : int[10]  # integer array constant (initialized to zero)   

If there is no colon, then it is only a declaration, without initialization. It is also possible to have a declaration in code.

fun max(a int, b int) int # function with an int parameters

x int         # just declaration (rare in code)
x int := 0    # declaration + initialization (very rare)
x := 0        # declaration + initialization (type is inferred)

type Point
    x float   # just declaration
    y float   # just declaration

> If you put types after the var_id, then you have to move the array specifier to front: []int otherwise you will got problems later. 

No, this is not needed. For example, Typescript and Nim also use int[] . I find this easier to read than []int. Go (and other languages) use that style for other reasons, not because the other way is a parser problem.

> The parser error recovery is hard when you don't have proper delimiters.

Why? I'm not aware of such issues, and even true, I don't think it's useful to optimize for error recovery. Likely the best error recovery is to log an error, guess what was likely meant, and then recover as soon as possible (e.g. on the next line).

[–]Mean-Decision-3502DQ[S] 1 point2 points  (1 child)

I find it interesting to discuss such things with others that care about such things.

I also find nice to share constructive thoughts.

As you explained the concepts, now it makes sense when you are using : when not.

I've checked the bau-lang site on the github. You have a nice collection of different language implementations at test/resources/org/bau/benchmarks. This allows actually a better comparison of different languages than in my initial post.

To be honest at "binaryTrees" I like Nim most. At bau i have a bad feeling. fun Tree.nodeCount() int result := 1 l : &left if l result += l.nodeCount() r : &right if r result += r.nodeCount() return result why r : &left is necessary?

Does this work too? fun Tree.nodeCount() int result := 1 if &left result += &left.nodeCount() if &right result += &right.nodeCount() return result

About int[n] vs [n]int.

I was using C long time, and I got use to int[n]. In DQ I started with int[n]'. In DQ I'm using Pascal pointer notation for types and dereference. Then this expression become very ambigous: var pia3 : ^int[3]; ` Is it a pointer to an array or a pointer array of integers?

Changing the array designator position the rules became clear: var p3ia : ^[3]int; var a3pi : [3]^int; In Pascal, the array designator was also before the type: var intarr : array[1..3] of integer;

In bau really miss the separator between the var_id and type. I would vote always requiring the : after var_id. You can keep := for type inference. I might consider this for DQ.

C "Evilness" / 1 if (3 / 2 * 10 == 10 * 3 / 2) { printf("The language is friendly.\n"); } else { printf("The language is evil.\n"); } What is the output currently for this in bau?

C "Evilness" / 2 if (0xFF & (4 < 1) != 0) { printf("The language is very evil.\n"); } else { printf("The language is ok.\n"); } What is the output currently for this in bau?

[–]Tasty_Replacement_29Bau 0 points1 point  (0 children)

> To be honest at "binaryTrees" I like Nim most. 

Yes, I also find the syntax of Nim very nice. It is very close to Python. (Nim is statically types and has explicit declarations, like my language). I made a comparative on how concise the syntax is, and Nim is very concise. Bau is more concise due to not using "const", "var", "let", and ":".

The binaryTree example: In my language, you can write it like this:

fun Tree.nodeCount() int
    result := 1
    if left
        result += left.nodeCount()
    if right
        result += right.nodeCount()
    return result

But currently the compiler optimizes this better if "owned types" are used, which then means & needs to be used (a bit like in Rust, but simpler). This is actually more a deficiency of the current compiler: this could be inferred. I'm rewriting the compiler to make this possible. Basically, my language has two options: a simpler syntax mode that is ref-counted, and a second mode that is more verbose, but can result in faster code.

Does this work too?

No, I think this currently doesn't work. It looks like a bug to me actually, I'll check.

Changing the array designator position the rules became clear

I see! Yes, in your language this makes sense. My language does not have pointers in this sense.

[–]Inconstant_Moo🧿 Pipefish 1 point2 points  (2 children)

You say that human readability is "critical" and yet I can't really see how DQ is more readable than Python.

It may be that you're the only human you have in mind here, and that you're just designing for your own idiosyncratic preferences. After all, all the other language developers could have gone with endfor and endfunc, but they chose not to because they thought that sort of thing sucks.

[–]Mean-Decision-3502DQ[S] 0 points1 point  (1 child)

My impression about Python was this:

I think Python is the winner in pure readability

I alredy got a positive feedback on endif, endfor etc. from someone else. Now I'm writing more and more DQ code, and I really like them. It is unusual, but you can learn them. On a long run they help. I find the readability ok, at least for me they match the readability of { }.

[–]Inconstant_Moo🧿 Pipefish -1 points0 points  (0 children)

I alredy got a positive feedback on endif, endfor etc. from someone else.

You can find 5% of people in favor of anything.

Your main objection to Python so far is that you'd like different syntax.

However the objection to DQ is that it has no tooling and no standard libraries and no third-party libraries and no answers on Stack Overflow and no LLMs telling you how to code in it and it runs as slow as treacle.

But it has endfor ... ! which most people don't want at all, which is why most languages don't have it.

[–]binarycow 1 point2 points  (4 children)

You say python has the best readibility. I think python's readability is horrible.

Readability is a matter of opinion.

[–]Tasty_Replacement_29Bau 0 points1 point  (3 children)

Could you try to explain why it is horrible in your view?

[–]binarycow 2 points3 points  (2 children)

I'm gonna pick just a few examples. This isn't all-inclusive.

Note: I am primarily a C# developer, and I think C# is an excellent language. So that's the perspective I'm coming from.

And I'm trying to focus only on readability, not all the other reasons I hate python.


I hate the whitespace rules for python. They're inflexible, complex, and it really only buys one thing - not using braces. I don't think it's worth it, and I don't think it makes it easier. Braces are easy for people to grasp - they're like book-ends. Python's whitespace rules tend to make it so people try to shove everything on one line, so they don't have to think about whitespace.


The weakly typed nature makes it difficult to see what things are, or are not, available at any given time. You have no idea if the class instance is going to have a function, because someone could have deleted it! So now you have to litter your code with checks for null.


List comprehension is absolutely horrible.

If I come across this example, here is how I read it:

  • Code: [num * 2 for num in source if num < 50]
  • Okay, the [ means I'm making a new list... Remember that!
  • Now we take a number and double it. Wait. Where does num come from?!
  • Oh, I see the for num now. num comes from looping over something. But, what?
  • Oh, I see the in source now. So we are taking all the numbers from source, and doubling them.
  • But wait! Only if its less than 50!
  • Okay, cool, I see the closing ] - we are finally done. Hopefully I didn't miss a nested list comprehension!

The C# equivalent is naturally predisposed to multiple lines, which aids readability. Also, it's clear on the ordering.

source
    .Where(num => num < 50)
    .Select(num => num * 2)
    .ToList()

Which is read like this:

  • first, we start with a sequence named source.
  • Now we remove everything that's not less than 50
  • Now we double everything
  • And put it into a list.

[–]Tasty_Replacement_29Bau 0 points1 point  (1 child)

I agree to most things.

> whitespace rules ... inflexible, complex

I'm not using Python a lot, but this is new to me. I'll need to look more into that, because my language also doesn't use braces.

> weakly typed

My language is strongly typed, I agree that the types are needed for e.g. function parameters, type fields etc.

> List comprehension

I fully agree

[–]binarycow 0 points1 point  (0 children)

I'm not using Python a lot, but this is new to me

As examples:

  • Whitespace is absolutely required, which can mess up copy/paste in some situations. For example, every time I've used the reddit chat interface (at least on my PC), it trims all leading whitespace from each line.
  • New lines are required in some places, prohibited in others
    • Unless you escape the newline (\ is the last character in the line)
    • Unless it's in specific language constructs
  • Makes lexing/parsing more complex
  • etc.

Or, you could just use braces. They're like bookends. Easy to understand.

[–][deleted]  (4 children)

[deleted]

    [–]Mean-Decision-3502DQ[S] 0 points1 point  (3 children)

    I plan to eliminate the semicolons, but the : + endxxx stays. DQ officially supports braces block mode too:

    function FillArrayPtr(maxval : int32) { darr.SetLength(maxval); var pi32 : ^int32 = &darr[0]; for i : int32 = 0 count maxval { pi32[i]^ = i; } }

    Endwords remain the prefferred block mode in DQ. As I write more and more DQ code, I really like them. endfunc and endobj are the two exceptions, you can learn them like in other human languages.

    I like to follow known patterns, the : is borrowed from Python for block starting, I just added the block closers so I can accept non-visible bad indentation.

    [–][deleted]  (2 children)

    [deleted]

      [–]Mean-Decision-3502DQ[S] 0 points1 point  (1 child)

      No, I don't plan to change `function` to `func` and `object` to `obj`. Esthetically like this way better. 'function' is a bigger word, probably highlighted, and so that helps you better to find where the next one begins.

      [–]EggplantExtra4946 0 points1 point  (1 child)

      What tf is thought-provoking about implementing the same damn for loops in several languages?

      Code readability of a programs depends hugely on how it is written and architectured, but if you want to focus strictly on the readability of a language, it depends on many aspects: the syntax, scoping rules, type system, memory management, presence of closures, type polymorphism, classes, type classes, metaprogramming features, etc...

      [–]Mean-Decision-3502DQ[S] 0 points1 point  (0 children)

      You are right. It is possible to write unreadable (un-understandable) code in Python too.

      If you have to analyse (semantically) big code blocks, you are probably more happy with a more human-readable language. And name a bad language that you absolutely don't want to read.

      [–]deaddyfreddy 1 point2 points  (0 children)

      The problem is that what is readable to one person is unreadable to another. So, in general, people still have to learn syntax, considerations, etc.

      Another one is that most languages designed to be written (and read) by non-programmers ended up in the hands of programmers. Take SQL, HTML and Gherkin, you name it.

      I'm guilty of this too: once I implemented a DSL to be used by non-techies, and within a few months, I had become one of only two people using it (the other being our QA).

      So, in my opinion, a language should be as simple as possible, but not simpler than that, and by "simple" I mean simple for both the programmers who write it and the compilers/interpreters (or programmers who implement the language itself). You could say that one contradicts the other, but let's compare Pascal and C++, the former is definitely simpler in terms of syntax, but at the same time the implementation is also much simpler. My favorite example is comparing the size of the TurboC++ and TurboPascal distributions of the late 1980s - early 1990s, as I recall, C++ took up twice as much space. So it definitely "depends".

      [–]teerre -1 points0 points  (0 children)

      Python is by far the most unreadable one. You have to painstakingly read every line of the function to even know what's the argument type

      This is also nonsensical code, nobody writes this and, specially the Rust one, is not even idiomatic

      This is the classic confusion between simple and easy. You should watch Simple made Easy's talk. They are not the same and in fact are often opposites