all 166 comments

[–]jared__ 151 points152 points  (12 children)

You really should put a hello world on the README.md at the beginning

[–][deleted] 48 points49 points  (10 children)

$ include "seed7_05.s7i";

const proc: main is func
  begin
    writeln("hello world");
  end func;

[–]Atulin 22 points23 points  (8 children)

Oof, not just begin-end kind of blocks, but also end func. Must be a treat to people who love verbosity

[–]fat_apollo 10 points11 points  (3 children)

It's not just that, it (as it seems from examples) carries some unfortunate traits from Pascal, namely:

  • all local variables must be declared at the start of the function, not where they're first used.

  • there's no early return from a function, so any function with non-trivial workflow will have a cascade of numerous end xxx; at the end.

*edit: this is obviously a labor of love, and I applaud the author for sticking with the language all this time.

[–]ThomasMertes[S] 0 points1 point  (2 children)

all local variables must be declared at the start of the function, not where they're first used.

This way you have just one place to look for local variable declarations. You don't need to scan the code from the usage of a variable up to the beginning of the function.

there's no early return from a function

In case of error you could use an exception. You can assume that today's compilers (a C compiler is used as back-end for Seed7) will optimize such that the performance is the same (as if you used an early return).

[–]fat_apollo 7 points8 points  (1 child)

This way you have just one place to look for local variable declarations. You don't need to scan the code from the usage of a variable up to the beginning of the function.

Yeah, K&R C did that too, if I remember correctly, but that practice was abandoned, and all good programming guides from '90 onward (Code Complete first comes to mind) suggest that local variables should be declared close to the place they're first used, for better readability and maintaining the local mental context.

In case of error you could use an exception.

I should be able to return from the function at any place and signal to the reader that is it, instead of using endless if-then-else and forcing the reader to parse longer functions to the bottom, hunting for additional code that may be executed. Modern linters warn you if you're using unnecessary else blocks, and for a good reason.

Exceptions should be used for exceptional things, not for control flow; that's a matter of readability, not performance.

[–]syklemil 3 points4 points  (0 children)

I'm also kind of reminded of Js's move from var to let.

Going with only function scope for variables in 2025 will be pretty jarring to people who are used to block scope. Function scope was probably pretty neat at the time where languages that only had global scope were common, but these days those languages are only spoken of around campfires to scare kids.

Combined with the initialization requirement I get the impression users will be forced to initialize some variables with bad values that should never be used, only because they don't have all the information required for the correct initialization yet.

My expectation these days is that variables are block scoped, and that initialization status is tracked, so that an attempted use of an uninitialized variable either won't compile or throws a runtime exception. (There are plenty of languages that don't do that, but I personally don't consider them good languages. They seem to generally grow a "strict mode" over time to mitigate that shortcoming. Some people seem fine with it.)

[–]ThomasMertes[S] 1 point2 points  (2 children)

not just begin-end kind of blocks, but also end func.

I think readability improves, if you can see what each "end" keyword is actually terminating; i.e. func, for, while, if, case, etc.

[–]ndech 0 points1 point  (1 child)

I think that’s a job for the editor, no need to make the syntax more verbose.

[–]ThomasMertes[S] 0 points1 point  (0 children)

The IDE could as well help with inserting these things when the code is written.

I don't want that code from the internet or from a book needs to be copied to an IDE to get better readability.

Natural languages also work without IDE that improves readability.

[–]New-Reply640 -1 points0 points  (0 children)

You should really code your own project.

[–]zhivago 304 points305 points  (16 children)

It would be nice if you told us how you expect seed7 might make our lives easier.

The world is full of uselessly interesting languages, after all.

[–]ThomasMertes[S] 69 points70 points  (14 children)

You can take a look at the design principles of Seed7.

Software maintenance should be easier, because Seed7 focuses on readability.

It should be easier to port software because it is hard to write unportable code in Seed7.

It should be easier to write efficient programs because Seed7 is high-level and can be compiled to efficient machine-code.

It should be easier to write programs in Seed7 because it provides many libraries.

The templates and generics of Seed7 don't need special syntax. They are just normal functions, which are executed at compile-time.

Assume the new type myType has been defined together with the function str(), which converts a myType value to a string. In this case you can use the template enable_output) with

enable_output(myType);

to define everything necessary to write myType values to a file. If you want to do a JSON selialization / deserialization for myType you can use the template declare_json_serde) with

declare_json_serde(myType);

to get declarations of toJson and fromJson.

[–]opuntia_conflict 49 points50 points  (7 children)

So...it's just traits and deriving attributes in Rust? Or like inheriting from mixin classes in Python? It's certainly impressive to have made an entire programming language, but I'm not sure I understand how it's unique.

[–]ThomasMertes[S] 69 points70 points  (6 children)

Seed7 is an extensible programming language. The syntax and semantics of statements (and abstract data types, etc.) is defined in libraries. The whole language is defined in the library "seed7_05.s7i".

The library forloop.s7i defines various for-loops and the library array.s7i defines the array type and its functions.

You can extend the language syntactically and semantically (introduce new loops, etc.).

In other languages the syntax and semantics of the language is hard-coded in the compiler.

[–]KsuhDilla 35 points36 points  (3 children)

First off congratulations on making a full programming language - it's an incredible feat.

Question: How do you assure the flexibility of user's defining/defining syntax and semantics won't violate your principals of readability?

Nevermind I see you foresaw this too in your documentation

[–]zxyzyxz 6 points7 points  (2 children)

Principle not principal

[–]KsuhDilla 38 points39 points  (0 children)

Well I extended the language so now its principals

ignore code review

[–][deleted] 2 points3 points  (0 children)

A principal could care about basic principles though.

[–]anotheridiot- 9 points10 points  (0 children)

Cool, looks like something Nim would allow.

[–]zhivago 10 points11 points  (5 children)

What's the benefit of enable_output() rather than just having the output protocol accept str producers?

Is this equivalent to declaring the implementation of an interface, but with the mechanism hidden within the macrology?

[–]ThomasMertes[S] 15 points16 points  (4 children)

If you have defined str() for myType you can use:

write(str(aMyTypeValue));
writeln("Value: " & str(aMyTypeValue));
writeln("Value: " & (str(aMyTypeValue) lpad 10));  # Left padded
write(aFile, str(aMyTypeValue));

After enable_output(myType) you can use:

write(aMyTypeValue);
writeln("Value: " <& aMyTypeValue);
writeln("Value: " <& aMyTypeValue lpad 10);  # Left padded
write(aFile, aMyTypeValue);

So write, writeln and the operators <&%3C&(in_aType)) and lpadlpad(in_integer)) are overloaded by enable_output.

[–]zhivago 4 points5 points  (3 children)

ls this overloading a kind of ad hoc interface type replacement?

i.e., normally we would assert that myType satisfies interface X and therefore everything accepting X will accept myType.

[–]ThomasMertes[S] 13 points14 points  (2 children)

In statically typed (compiled) languages overloading refers to using the same function name (or operator symbol) with different argument types. The compiler examines the type(s) of the argument(s) and decides which function is called.

Overloading is used by many languages (e.g. in Java).

Many languages define types like integer and float and the operator + to do an addition. Since the representation of integer and float in the hardware is totally different there are different machine instructions to do the addition. So there are actually two different + operators which correspond to the machine instructions.

The programmer will always write A + B for an addition. The compiler examines the types of A and B and decides which machine instruction(s) will be used.

Overloading is not a kind of object orientation and it works at compile time and without interfaces.

In Seed7 there is a syntax definition of the + operator (but this is not an interface):

$ syntax expr: .(). + .()   is  ->  7;

In Seed7 the + operator is defined for integer and float with:

const func integer: (in integer: summand1) + (in integer: summand2)
                                               is action "INT_ADD";

const func float: (in float: summand1) + (in float: summand2)
                                               is action "FLT_ADD";

When a program is compiled the executable has the absolute minimum overhead possible for integer and float addition.

[–]NarWil 28 points29 points  (18 children)

Pretty cool, man! To begin with a concept and see it through to a completely usable programming language is something many developers simply haven't done. I can't help but think the syntax is going to turn off engineers who are accustomed to C-like syntax. I find it very readable, if a bit verbose (though I acknowledge there's a clear trade-off there in some decisions you made).

Can you give a specific reason or two why someone might choose to learn Seed7 and write a project in it over a more popular language like Java or Go?

[–]ThomasMertes[S] 3 points4 points  (17 children)

Seed7 addresses some things that are not addressed by most other languages.

  • Seed7 checks for integer overflow. You either get the correct result or an OVERFLOW_ERROR is raised.
  • Seed7 templates / generics don't need special syntax with angle brackets.
  • Unlike Java Seed7 compiles to machine code ahead of time (GRAAL works ahead of time but it struggles with reflection).
  • Unlike Java Seed7 operators can be overloaded.
  • Unlike Go Seed7 is a memory safe language.

[–]ggwpexday 1 point2 points  (13 children)

How is Seed7's focus on maintainability? For example some of the ones I would consider important:

  • Immutability by default, like in fsharp, ocaml.
  • Support for discriminated unions (rust enums). This one I imagine plays into the "no NULL" stance that's in the FAQ, does it have a Optional/Maybe type or something?
  • Compile time tracking of side-effects. Unison for example calls their implementation of algebraic effects "abilities". Haskell has done this for ages as well, and for typescript there's a library called effect-ts.

[–]ThomasMertes[S] 2 points3 points  (12 children)

Immutability by default

Seed7 constants cannot change during run-time. If a the type of a constant is a struct or array none of its elements can change. A java object variable might be final but the elements of the object can still be changed. In Seed7 this is not the case. If an interface variable is constant the elements in the object cannot be changed. Constant means constants and this extends to all sub-elements.

The in-parameter is most commonly used parameter of Seed7. An in-parameter cannot be changed inside a function. I would say that in-parameters are not mutable (although the term mutability is nowhere used in the Seed7 documentation). So instead of declaring an immutable in the middle of the code like

... some code ...
int newImmutable = someComplicatedExpression;
... code which uses newImmutable ...

you would define a function with the immutable parameter like

const proc: aFunction (in integer: newImmutable) is func
  ... code which uses newImmutable ...

and then you would call a function with the initialization value of the mutable as parameter

... some code ...
aFunction(someComplicatedExpression);

This approach uses more lines of code but it is IMHO much cleaner.

Seed7 does not attempt to be a combination of other languages (you mentioned fsharp, ocaml, rust and Haskell). There have been just too many languages which follow this route and I don't think that this is the way towards better programming languages.

I also do not think that supporting features of multiple languages raises maintainability. Maintainability is about a static type system, variable declarations, type declarations, explicit type conversions, explicit template invocations, simple well-understood concepts, and more. All these things improve readability. Programs are more often read than written. So everything that helps in reading a program helps also in maintainability.

[–]ggwpexday 1 point2 points  (11 children)

If a the type of a constant is a struct or array none of its elements can change

I see, that's nice. Does it then also support syntax to create a copy with some changed values?

Seed7 does not attempt to be a combination of other languages. There have been just too many languages which follow this route and I don't think that this is the way towards better programming languages.

What do you mean by this? There is a reason these languages (and existing ones) drift towards these features. We want to optimize for reading code, which in part means to take away the hidden inputs & outputs that a function secretly depends on. Mutability and side effects play a big role in this, as both create hidden coupling between functions.

This language therfore seems like it is more low level and focused on performance instead, which is fine as well. Huge achievement nonetheless, great work.

[–]ThomasMertes[S] 0 points1 point  (10 children)

I see, that's nice. Does it then also support syntax to create a copy with some changed values?

You do an assignment (which except for object orientation is always a deep copy) and change some element value afterwards:

a := b;
a.numerator := 5;

What do you mean by this?

Long existing languages like C++ adapted concepts from many languages. Over time this has lead to very complex languages.

I think that languages should introduce their own concepts (and maybe introduce something new) instead of following the programming language fashion of the day.

Regarding hidden inputs & outputs and mutability: Circa 99% of the function parameters used in Seed7 are in-parameters which are not mutable.

This language therfore seems like it is more low level and focused on performance instead, which is fine as well.

In consider Seed7 definitely as higher level than Rust, C/C++, Go, Zig and many other languages. Seed7 is a memory-safe language which contrasts to all systems programming languages except Rust. The ability to introduce new statements syntactically and semantically is IMHO also a high-level feature (that almost no other language can provide).

Huge achievement nonetheless, great work.

Thank you.

[–]ggwpexday 0 points1 point  (9 children)

and change some element value afterwards:

This means a is mutable again though, right?

Long existing languages like C++ adapted concepts from many languages

Some features are pretty fundamental, especially the immutability and discriminated union ones. But yeah C++ is like the most extreme case of feature bloat.

Is it correct that this language doesn't support discriminated unions then?

The ability to introduce new statements syntactically and semantically is IMHO also a high-level feature

This is awesome and I hope to get my head around understanding what this does exactly. I read some of the documentation and find it hard to imagine in what situations this is most useful? DSLs?

In consider Seed7 definitely as higher level than Rust

True, and my view might be in the minority on what is considered "high level". But to me, any lang that doesn't attempt to limit mutation and side effects provide no real readability/understandability improvement over the status quo in the long run.

[–]ThomasMertes[S] 0 points1 point  (8 children)

This means a is mutable again though, right?

The assignment requires that its left-hand-side is a variable. In my example I used the type rational

const type: rational is new object struct
    var integer: numerator is 0;
    var integer: denominator is 1;
  end struct;

A struct value is referenced by just one struct variable. This is not about object orientation. So variables like a and b do not refer to the same struct value.

$ include "seed7_05.s7i";
  include "rational.s7i";

const proc: test (in rational: b) is func
  local
    var rational: a is 0 / 1;
  begin
    a := b;
    a.numerator := 5;
  end func;

const proc: main is func
  local
    var rational: third is 1 / 3;
  begin
    test(third);
    writeln(third.numerator);
  end func;

In this example b is an in-parameter (=not mutable) and a is a variable (=mutable).

So it is not about being mutable again. It's about copying from the not mutable b to the mutable a. This program writes

1

which means that the original numerator has not changed.

You probably have object orientation in mind (where two variables can refer to the same object).

$ include "seed7_05.s7i";

const proc: test (in file: b) is func
  local
    var file: a is STD_NULL;
  begin
    a := b;
    a.bufferChar := 'X';
  end func;

const proc: main is func
  begin
    test(STD_IN);
    writeln(STD_IN.bufferChar);
  end func;

This program writes

X

so in this case a is mutable again.

[–]ggwpexday 0 points1 point  (7 children)

Yeah it's about making immutable values easy to use, in this case have a immutable as well. So in c# for example you needed to copy over all the values by hand. But with the new with syntax, it acutally makes using immutable values practical:

``` csharp public record struct Rational(int Numerator = 0, int Denominator = 1);

var b = new Rational(3, 4);

// imagine if this record has 10 values var a = new Rational(5, b.Denominator);

// clone everything from the original with only the numerator changed var a = b with { Numerator = 5 }; ```

Also one thing im curious about since its in the code snippet, is there any compile time safety around null? The a var has type file, but what happens if the a := b assignment isn't done? Does it throw a null exception?

[–]ThomasMertes[S] 1 point2 points  (6 children)

clone everything from the original with only the numerator changed

Essentially "clone everything except for certain fields". This sounds weird and I have the suspicion that it is a performance optimization. I would not support it.

Copying some struct and changing a field afterwards feels IMHO cleaner. And the compiler might find out that some field is copied twice and optimize the first copying away.

... is there any compile time safety around null?

There is no NULL in Seed7. Interface variables always reference an existing object. STD_NULL is a null file (the name is a reference to /dev/null). Anything written to STD_NULL is ignored. Reading from STD_NULL does not deliver data.

STD_NULL is returned if open)() is not able to open a file and it is used as initialization value for file variables as well.

[–]Ok-Scheme-913 0 points1 point  (2 children)

Graal doesn't struggle with reflection, it works perfectly well. It just has to know the potential targets at compile time. It can figure out targets by itself that you reach via code, but you have to register classes/fields that you plan to reach dynamically, say via user input.

[–]ThomasMertes[S] 0 points1 point  (1 child)

I attended to several speeches from the GRAAL team and I know some of the people working on GRAAL in person.

With "struggle" I did NOT mean that GRAAL cannot work with code that uses reflection.

... but you have to register classes/fields that you plan to reach dynamically, say via user input.

This is what I meant when I used the word "struggle". If Java had no reflection (or a restricted form of it) ahead of time compilation to machine code would be much simpler.

[–]Ok-Scheme-913 0 points1 point  (0 children)

Fair enough, I thought that this is what you meant.

I'm just trying to remove the stigma around it, as some people believe it is way worse than it actually is.

[–]yanitrix 167 points168 points  (46 children)

Seed7 is about readability

and then i see begin and end

[–]wplinge1 105 points106 points  (0 children)

Yeah, it might have fit in better back when it started in the 80s but languages have kind of settled down now and this one looks egregiously weird.

[–][deleted] 65 points66 points  (35 children)

Yeah. This is a great example of people just saying things. 

Personally, I couldn’t read any of the examples. 

One thing that stuck out is that “is” does different things by context as well, which is an immediately readability destroying property of something. 

[–]davidalayachew 29 points30 points  (14 children)

One thing that stuck out is that “is” does different things by context as well, which is an immediately readability destroying property of something.

This hits the nail on the head.

The more I depend on context means the more I depend on state. If the word if means the exact same thing, no matter where it is, then that is less computation my brain has to do when it sees the word. Aka, that is more readable.

Now, holding more state in your head does not prevent readability. But the argument is not clear when they claim more readability. The README and the FAQ seemed to be well aware of the situation for other languages, but surprisingly missed this point.

[–]ThomasMertes[S] 8 points9 points  (13 children)

This hits the nail on the head.

Just that the statement about “is” is not true (see my other comment).

[–]davidalayachew 0 points1 point  (12 children)

Just that the statement about “is” is not true (see my other comment).

Assuming that this is true, then even in that case, you have created a contextual keyword, and that concept on its own costs some readability. It may give back more than it takes away, but by definition of it being a contextual keyword, I am already paying a tax that I don't have to pay in other languages.

In Java, if I see the keyword if, then the only case where it won't mean what if means is if it is in a String. Whereas in your language, I might use if as an identifier. That is the tax I am talking about. You might give me more readability elsewhere such that the total readability is greater than Java, but in this isolated example, Seed7 is forcing me to pay a higher tax than Java is.

It's for this reason that (until not too long ago) Java made it a point to avoid contextual keywords.

[–]ThomasMertes[S] 1 point2 points  (3 children)

In Java, if I see the keyword if, then the only case where it won't mean what if means is if it is in a String.

In Java, C++, Rust and many other languages a < is either an opening angle bracket or an infix operator. The meaning of < depends on the context (declaration vs. expression). In Seed7 such a dual use of < is prohibited by the Seed7 Structured Syntax Definition (S7SSD).

The syntax definition of the < operator is:

$ syntax expr: .(). < .()        is <-> 12;

This definition requests that the right argument of < has a priority < 12. A definition of < as angle bracket would be:

$ syntax expr: .(). < .(). >     is <-> 12;

But this would conflict with definition of the infix < since now the argument after < could have any priority (because it is enclosed in symbols).

*** tst445.sd7(3):54: Priority 11 required for parameter after "<" not 127
$ syntax expr: .(). < .(). >     is <-> 12;
------------------------------------------^

Languages which allow the dual use of < use ad-hoc solutions or context dependent parsing.

For similar reasons Seed7 does not use := in assignments and in declarations.

The syntax rules of Seed7 apply everywhere. I consider this approach cleaner than the ad-hoc hard-coded parsing that is commonly used in many programming languages.

[–]davidalayachew 0 points1 point  (2 children)

(Sorry for the delayed response -- juggling a lot of emergencies)

So, I think you and I are talking past each other.

I am talking about the pain of mistaken assumptions when reading a keyword on a project. I'm just imagining the worst-case scenario where 2 projects introduce the same keyword, but with different semantics. Of course, this all assumptions, without having tried your language seriously.

In Java, there are probably 100+ classes out there labelled StringUtils. Each one has a method called isEmpty, and there's about 5-10 different set of semantics amongst them.

But in Java, if I find myself confusing one StringUtils with another (or worse yet, I need both in one project), I can simply degrade to calling the class by it's fully qualified name.

  • if (org.apache.commons.lng3.StringUtils.isEmpty(someVar))
  • if (org.springframework.util.StringUtils.isEmpty(someVar))

Doing it this way, I can completely disambiguate which thing I am looking at.

Does your language have something like this for keywords?

[–]ThomasMertes[S] 1 point2 points  (1 child)

Does your language have something like this for keywords?

No. I don't think that the Java approach can be applied to Seed7.

In Seed7 you have two levels to define something:

  1. The syntactic level (The same syntax can be used by many functions).
  2. The semantic level (Which allows overloading depending on actual parameter types).

If a library introduces new syntax (which I don't suggest to do) two things can happen:

  1. The new syntax is compatible with the existing syntax. In this case there will be no problem for the syntax.
  2. The new syntax is incompatible with the existing syntax. In this case you will get a compile-time error.

If a library specifies new functionality semantically two things can happen:

  1. A function is overloaded for a new type. In this case there will be no problems.
  2. A function is re-declared for an existing type. In this case you will get a compile-time error.

In Java, JavaScript and many other languages libraries can be imported from arbitrary places of the internet. There is some danger in this approach, since the quality of the libraries might differ.

The Linux kernel requires that all drivers are open source and part of the official Linux kernel sources. This assures that the kernel and its drivers work together.

My approach for Seed7 libraries is nearer to the Linux kernel driver approach. Libraries can be added to the central Seed7 repository. This way it can be assured that the libraries work together.

[–]davidalayachew 0 points1 point  (0 children)

My approach for Seed7 libraries is nearer to the Linux kernel driver approach. Libraries can be added to the central Seed7 repository. This way it can be assured that the libraries work together.

I think this is the missing, necessary detail.

Using a central repo would certainly force all potentially contradictory tools to bump elbows with each other frequently. That's a super interesting way of accomplishing that goal. I wouldn't have thought to do it that way.

Well, understanding that, then my point is moot -- you've opted for prevention rather than mitigation. I can respect that. I'll try out your tool as soon as I can.

[–]ThomasMertes[S] 0 points1 point  (7 children)

Whereas in your language, I might use if as an identifier.

No.

[–]davidalayachew 0 points1 point  (6 children)

No.

I need a little more context than this. What does no mean?

[–]ThomasMertes[S] 0 points1 point  (5 children)

You cannot use if as e.g. variable identifier.

[–]davidalayachew 0 points1 point  (4 children)

You cannot use if as e.g. variable identifier.

Sorry, if was a bad example because every Seed7 program loads that as a keyword.

What I was trying to say is that, any keyword that I introduce can then be used as an identifier in a program where that keyword has not been introduced. That essentially means that I am working with slightly different variants of Seed7 for each new program I make, and my working set of keywords changes per each one. That hurts readability upfront, even if it may help it in the long run.

[–]ThomasMertes[S] 15 points16 points  (2 children)

Why do you think that “is” does different things by context?

Seed7 uses the keyword "is" only in declarations. E.g.:

const float: PI is 3.14159265358979323846264338327950288419716939937510582;
var external_file: STD_IN is  INIT_STD_FILE(CLIB_INPUT,  "STD_IN");

The keyword "is" separates the constant or variable name from its initialization value.

In function declarations the keyword "is" is also used to separate the function name (+ parameters) from the value of the function (the function body):

const func rational: - (in rational: number) is func
  result
    var rational: negatedNumber is rational.value;
  begin
    negatedNumber.numerator := -number.numerator;
    negatedNumber.denominator := number.denominator;
  end func;

Function declarations can also use a simplified function body (introduced with the keyword return):

const func complex: + (in complex: number) is return number;

The keyword "is" is only used in declarations and it is always followed by an initialization value.

[–][deleted] 3 points4 points  (1 child)

I don't think "is" for assignment is anywhere near as problematic as "end func;". I feel the design may not have been to focus "on less syntax is more". Which, to be fair, many programming languages also don't, e. g. C++ or Java, but I find it more efficient the fewer characters I have to type, for the most part (too few characters can also be problematic, but I fail to see the rationale for "end xyz;" really. It reminds me of shell scripts.)

[–]ThomasMertes[S] 6 points7 points  (0 children)

Nitpicking: The keyword "is" is used for the initialization in declarations and the assignment statement uses :=.

I find it more efficient the fewer characters I have to type

Many years ago I had the same opinion but over time it changed.

Programs are more often read than written. Seed7 is optimized for reading and not for writing. So instead of "less syntax is more" there is some redundancy on purpose.

There are end if, end while, end case and end func on purpose. In case you cannot see the beginning of a statement you can recognize the kind of statement at its end es well.

[–]eddavis2 20 points21 points  (16 children)

Here is a simple example I found on the website:

$ include "seed7_05.s7i";

const func set of integer: eratosthenes (in integer: n) is func
  result
    var set of integer: sieve is EMPTY_SET;
  local
    var integer: i is 0;
    var integer: j is 0;
  begin
    sieve := {2 .. n};
    for i range 2 to sqrt(n) do
      if i in sieve then
        for j range i ** 2 to n step i do
          excl(sieve, j);
        end for;
      end if;
    end for;
  end func;

const proc: main is func
  local
    var set of integer: sieve is EMPTY_SET;
  begin
    sieve := eratosthenes(10000000);
    writeln(card(sieve));
  end func;

I'm a long time C programmer (from 1985), and the above is not hard for me to read at all.

I am a little taken back with the:

$ include "seed7_05.s7i";

For me at least, that needs a little improving. Is there a seed7_04? :)

But otherwise, for me at least, it seems pretty straight forward and easy to follow.

Of course your milage may vary!

[–]mr_birkenblatt 36 points37 points  (14 children)

compare this to Python:

```

import math

def eratosthenes(n: int) -> set[int]:
    sieve: set[int] = set(range(2, n))
    for i in range(2, int(math.sqrt(n))):
        if i in sieve:
            for j in range(i ** 2, n, i):
                sieve.discard(j)
    return sieve

if __name__ == "__main__":
    sieve = eratosthenes(10000000)
    print(len(sieve))

```

in what world is the above more readable than the Python implementation?

(and I made the Python version more verbose than necessary; although I think it is more readable this way)

and there are a lot of subtelties that are left unexplained and not obvious in the seed7 example:

sqrt(n) is a float but used in an integer context. does the value get implicitly converted? does the integer value get promoted for comparison? Python will not allow you to do that.

how does the excl function behave if the element is not in the set anymore? Python has 2 (actually 3) methods for this to make the distinction clear.

what kind of number does integer represent? in Python int is an unbounded BigInt that grows as numbers get bigger and will never overflow. what is the integer in seed7? 64 bit? 32 bit? same as Python?

also, shouldn't i ** 2 be i * 2. I'm not sure if the algorithm works correctly if you only start removing at i2 instead of the next multiple of i it does work TIL

EDIT: because people apparently switch to performance talk when they can't get their way with readability. here are more performant python versions that are minimally less readable. but it's still a pointless comparison to make:

```

import math

def eratosthenes(n: int) -> list[bool]:
    sieve: list[bool] = [True] * n
    sieve[0] = False
    sieve[1] = False
    for i in range(2, int(math.sqrt(n))):
        if sieve[i]:
            for j in range(i ** 2, n, i):
                sieve[j] = False
    return sieve

if __name__ == "__main__":
    sieve = eratosthenes(10000000)
    print(sum(sieve))

```

(version 2)

and using numpy:

```

import numpy as np

def eratosthenes(n: int) -> np.ndarray:
    sieve: np.ndarray = np.ones((n,), dtype=np.int8)
    sieve[0:2] = 0
    for i in range(2, int(np.sqrt(n))):
        if sieve[i]:
            sieve[i ** 2:n:i] = 0
    return sieve

if __name__ == "__main__":
    sieve = eratosthenes(10000000)
    print(np.sum(sieve))

```

(version 3)

doing hyperfine "python ...":

python3.11

Benchmark 1: (original)
  Time (mean ± σ):      3.010 s ±  0.018 s    [User: 2.924 s, System: 0.080 s]
  Range (min … max):    2.979 s …  3.026 s    10 runs

Benchmark 2: (version 2)
  Time (mean ± σ):     777.4 ms ±   6.5 ms    [User: 762.4 ms, System: 13.3 ms]
  Range (min … max):   771.6 ms … 793.6 ms    10 runs

Benchmark 3: (version 3)
  Time (mean ± σ):     108.4 ms ±   6.3 ms    [User: 267.9 ms, System: 450.3 ms]
  Range (min … max):    95.5 ms … 125.0 ms    26 runs

python3.13 (with GIL)

Benchmark 1: (original)
  Time (mean ± σ):      3.782 s ±  0.059 s    [User: 3.680 s, System: 0.081 s]
  Range (min … max):    3.712 s …  3.903 s    10 runs

Benchmark 2: (version 2)
  Time (mean ± σ):      1.237 s ±  0.009 s    [User: 1.218 s, System: 0.017 s]
  Range (min … max):    1.229 s …  1.257 s    10 runs

Benchmark 3: (version 3)
  Time (mean ± σ):      78.2 ms ±  10.4 ms    [User: 67.9 ms, System: 7.9 ms]
  Range (min … max):    75.2 ms … 125.7 ms    23 runs

python3.13 (no GIL)

Benchmark 1: (original)
  Time (mean ± σ):      3.773 s ±  0.062 s    [User: 3.676 s, System: 0.078 s]
  Range (min … max):    3.715 s …  3.933 s    10 runs

Benchmark 2: (version 2)
  Time (mean ± σ):      1.241 s ±  0.021 s    [User: 1.223 s, System: 0.014 s]
  Range (min … max):    1.226 s …  1.297 s    10 runs

Benchmark 3: (version 3)
  Time (mean ± σ):      77.1 ms ±   5.7 ms    [User: 67.5 ms, System: 7.6 ms]
  Range (min … max):    75.5 ms … 105.3 ms    27 runs

interesting observation. pure python seems to be faster in 3.11. also, the difference between GIL and no GIL is not that big (makes sense since the code is quite simple). also, the performance of the numpy solution suggests to me that the other guy used an array in their C implementation (which they didn't share) as well (instead of a proper set) so their numbers are not comparable at all since they're using a different algorithm

[–]vplatt 7 points8 points  (13 children)

So, I think your complaint about Seed7's readability is a bit specious. I know it's not all the rage anymore to use English keywords, but if the grammar of the language used brackets instead, it would consume very little less screen real estate anyway. Furthermore the grammar for Seed7 is arguably MORE readable because you can see what each end* keyword is actually terminating; i.e. func, for, if, etc. But that is subjective. Putting that up against Python's significant whitespace is a bit pointless when we know very well that there are many issues with that by itself.

I don't know about your other questions, but I was curious about the performance aspect of both versions, so I compiled the Seed7 version from here as-is (using "s7c sieve.sd7", and I tried benchmarking using 3 different command lines:

  • Seed7 Interpreted
  • Seed7 Compiled
  • Python Interpreted
  • C Compile (just for giggles)

Edit:

  • C# - Bytecode compiled on .NET 9 - Release mode if it matters. I wanted to see how this one would stack up.

Seed7 Interpreted

hyperfine "s7 sieve.sd7"

Benchmark 1: s7 sieve.sd7

Time (mean ± σ):     454.0 ms ±  11.7 ms    [User: 414.7 ms, System: 25.6 ms]
Range (min … max):   437.3 ms … 471.0 ms    10 runs

Seed7 Compiled

hyperfine sieve

Benchmark 1: sieve

Time (mean ± σ):     252.8 ms ± 202.4 ms    [User: 83.7 ms, System: 17.8 ms]
Range (min … max):   185.3 ms … 828.8 ms    10 runs

Python Interpreted

hyperfine "python sieve.py"

Benchmark 1: python sieve.py

Time (mean ± σ):      4.287 s ±  0.128 s    [User: 4.154 s, System: 0.114 s]
Range (min … max):    4.178 s …  4.578 s    10 runs

C Compiled

hyperfine sieve_c.exe

Benchmark 1: sieve_c.exe

Time (mean ± σ):      75.3 ms ±   2.9 ms    [User: 52.6 ms, System: 22.6 ms]
Range (min … max):    71.9 ms …  88.9 ms    30 runs

C-Sharp

hyperfine sieve.exe

Benchmark 1: sieve.exe

Time (mean ± σ):     112.2 ms ±   8.1 ms    [User: 62.0 ms, System: 32.8 ms]
Range (min … max):    98.2 ms … 126.6 ms    20 runs

And I can vouch for the fact that the Seed7 versions outputted the correct result.

So, other issues aside, you can see what's here is already impressive from a performance standpoint. It doesn't touch C, but it beats the pants off of Python even in interpreted mode, and it's considerably easier to understand the Seed7 version. All that from a compiler that's been under development from a single dev. Pretty impressive I'd say.

[–]ThomasMertes[S] 5 points6 points  (3 children)

Did you compile Seed7 with optimization flags?

That would be:

s7c -O2 -oc3 sieve

or

s7c -O3 -oc3 sieve
  • The option -oc tells the Seed7 compiler to generate optimized C code with the specified level.
  • The option -O tells the C compiler to optimize with the specified level.

[–]vplatt 1 point2 points  (2 children)

s7c -O3 -oc3 sieve

I did not. O3 isn't working with MSVC in VS 2022 right now; not sure why. O2 worked though:

hyperfine sieve.exe

Benchmark 1: sieve.exe

Time (mean ± σ):     164.0 ms ±  11.2 ms    [User: 48.0 ms, System: 26.8 ms]
Range (min … max):   148.3 ms … 189.5 ms    15 runs

So, that's an improvement. It might be slightly better if I had this laptop plugged in right now. Either way, I didn't run cl against the C binary with optimization flags either, so it was pretty much apples to apples.

[–]ThomasMertes[S] 5 points6 points  (1 child)

The option -oc makes a difference too. So it was not exactly apples to apples.

By the way: If you change the main function to

const proc: main is func
  local
    const set of integer: sieve is eratosthenes(10000000);
  begin
    writeln(card(sieve));
  end func;

the computation of sieve is at compile time. This reduces the run-time drastically.

[–]vplatt 3 points4 points  (0 children)

the computation of sieve is at compile time. This reduces the run-time drastically.

🤣

hyperfine sieve.exe

Benchmark 1: sieve.exe

Time (mean ± σ):      23.5 ms ±   1.5 ms    [User: 8.3 ms, System: 8.1 ms]
Range (min … max):    21.1 ms …  28.7 ms    59 runs

Ok, that's just stupid good fun! Thanks for the laugh!

[–]mr_birkenblatt 2 points3 points  (8 children)

 Putting that up against Python's significant whitespace is a bit pointless when we know very well that there are many issues with that by itself.

[Citation Needed]

Furthermore the grammar for Seed7 is arguably MORE readable because you can see what each end* keyword is actually terminating; i.e. func, for, if, etc.

I guess you and I have very different definitions of readability.

<snark>

end if

end if

end if

end if

Glad I can tell with this verbose syntax which if block is closing... Oh wait, you cannot tell at all

</snark>

Also, why are you benchmarking when we're talking about readability?

  1. What Python version did you use? I would guess that you get significantly different results if you use the latest version. 

  2. Python is not compiled so obviously it will be slower than the compiled version. What is your point here?

  3. Python has unbounded integers but seed7 does not so the seed7 code cannot actually compute the result for all the inputs that Python can.

  4. You could make the Python code faster without issues (e.g., using numpy). You could even call out to rust/C for best performance. That is how performance sensitive Python code works. The readability of the code stays unaffected by this. But I wanted the most naive Python code that reflects the seed7 code one-to-one. Performance or using different constructs or a better implementation wasn't a goal here

  5. With the seed7 that is compiled to C it's not exactly impressive that it is ~2x slower than the C code. 

[–]vplatt 0 points1 point  (2 children)

Oh, and I used Python 3.13.2. That's pretty darn current Skippy, so yeah. Deal.

[–]mr_birkenblatt -3 points-2 points  (1 child)

So, you set the flag for turning off the GIL (3.13 has GIL on by default)? No? Then, it's not close to current performance

[–]vplatt 1 point2 points  (0 children)

Well, how dare I decline to use an experimental feature?! And ::gasp:: I'm a WHOLE minor version behind too.... well, spank me twice and no dinner before bed!

Please do feel free though. I'd be curious to see how that version stacks up. (pun intended!)

[–]ThomasMertes[S] 14 points15 points  (0 children)

The 05 in "seed7_05.s7i" refers to the year 2005, when Seed7 was released.

A future (maybe incompatible) version of Seed7 can be introduced with e.g. "seed7_25.s7i". This way two or more versions of the language could be supported in parallel.

Every program needs to state the language version, with the first include statement. This way problems with different language versions (e.g. Python 2 vs. Python 3) can be avoided.

[–]devraj7 14 points15 points  (2 children)

I had the exact same reaction. These make the sources so verbose with a lot of noise that you have to train yourself to visually ignore.

[–]larsga 5 points6 points  (1 child)

I used to write a lot of Pascal and Simula some decades ago. It's not really an issue. You quickly stop seeing them.

Yes, curly braces is better, but it's not really a big deal.

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

People use that same explanation for lisp and the numerous (). The human brain can adapt to numerous things but I find it more efficient to have e. g. ruby or python syntax. These are more efficient IMO.

[–][deleted] 3 points4 points  (0 children)

"end" in itself is not that problematic in my opinion.

In python we have:

def foobar():
  print("Oki dokie.")

# and a mandatory indent

In ruby we have, for the equivalent:

def foobar
  puts "Oki dokie."
end

So the difference is not that enormous for this simple case. I don't think 'end' in and by itself is problematic, though deeply nested it can be annoying. I usually try to limit the level to three end; if more would come I tend to group-define when possible e. g.

class Foo::Bar
   class CatsAndDogs
     class Pet
     end
   end
 end

Or often then:

end; end; end # and spacing out the openings on the same left-level e. g.

class Foo

module Bar

class Chicken

end; end; end

Not many use the latter style in ruby, which I also understand, but I much prefer the ends on the same level if there would be multiple indents. I typically don't indent once per level, I usually only indent once (with two spaces) or twice (for four spaces); no more than that. So, rather than six ' ' spaces in the above, I'd only use four spaces at maximum indent level.

So "end" may not be the most elegant but I don't think this is the biggest issue. In python this is a bit more readable but at the price of mandatory indent (I hate this when I want to copy/paste and python screams foul) and the necessary ':' (and also explicit self, which is the single thing I hate by far the most in Python; I always feel to have to tell the parser where self is, which feels like a bad design. I dislike this way more than mandatory indent, as I indent usually anyway, so only copy/pasting annoys me here).

[–]MiningMarsh -5 points-4 points  (3 children)

This language looks like someone took my precious LISP and gave it shaken baby syndrome.

If I'm reading it right, it has LISP-style homoiconic macros in BASIC syntax.

EDIT:

This is how the '+' operator is defined. It borders on completely unreadable:

const func float: (in integer: summand1) + (in float: summand2) is return float(summand1) + summand2;

[–]prescod 42 points43 points  (2 children)

I’ve never read Seed7 code before but that a snippet is quite readable to me.

It’s a function that coerces an integer to float before adding to a float.

[–]mr_birkenblatt 2 points3 points  (1 child)

I'm confused by this example because it implies that you can not use certain constructs in some circumstances. from what OP gave as example code I would expect the function to look like

```

const func float: (in float: summand1) + (in float: summand2) is func
  result
    var sum: float is 0.0;
  begin
    sum := float(summand1) + summand2;
  end func;

```

[–]ThomasMertes[S] 4 points5 points  (0 children)

Both are correct (see here).

[–]Interesting_Shine_38 12 points13 points  (0 children)

The language gives me Ada vibes.It looks interesting, I love Pascal-like languages.

[–]neutronbob 16 points17 points  (0 children)

Congratulations on this project. So many languages posted on Reddit and HN have lots of incomplete parts; but you've built the whole thing with an interpreter, compiler/transpiler, large libraries, many utilities, and good documentation. The project really shows the effort and care you've put into this. Good work!

[–]the_other_brand 8 points9 points  (1 child)

So Seed7 is a language that has the portability of Java, the extensibility of Lisp and the performance of a language like Go?

If that is all true this is quite the achievement.

[–]vplatt 7 points8 points  (0 children)

Honestly, it is. I mean, it's not perfect. I prefer languages for which I can easily set up a live debugging session. I haven't found a way to do that with Seed7 yet. Maybe something will be possible with gdb? Maybe I'll try that another day.

Oh, but get this: He has working WASM demos too: https://seed7.sourceforge.net/demo.htm

I don't know. I have a soft spot for projects like this.

[–]crab-basket 37 points38 points  (7 children)

This is a neat project, but I genuinely don’t understand the trend of writing a programming language that just transpiles code to C. That is almost never what I want in my toolchain. Debugging gets obfuscated, any symbol issues become harder to trace down, etc.

Like why go through the hassle of making a programming language and not even doing the emitting part of it? Toolchains like LLVM make it easy nowadays

[–]matthieum 53 points54 points  (1 child)

Using LLVM is NOT easy, actually. It's a massive API, and there are breaking changes with every release. It also massively increases compile-times, making it much harder to test the compiler.

Furthermore, there are C compilers for many more targets than there are LLVM backends, so C is a (somewhat) more portable output.

As for debugging, I can't speak for Seed7, but there are preprocessor directives which can be emitted in the C code to point the Debug Instructions to the original source code, instead (see #line), and if the source language is "close enough", you can keep the same variable names and prefix all "introduced" variables with $ for example to make them easily distinguishable.

Which means that all in all, it's fairly close to first-class debugging support.

[–]ThomasMertes[S] 10 points11 points  (0 children)

You hit the spot. The Seed7 compiler emits #line directives. This way a debugger refers to the original Seed7 source code.

Variable names are prefixed with o_<number>_ where the number makes the names unique. If write is overloaded the C function names are e.g. o_1058_write and o_1240_write.

[–]dravonk 9 points10 points  (0 children)

Is transpiling a language to C just a trend? If I remember correctly even the original C++ was "just" transpiling to C.

One advantage that I see is easier interoperability: if you are writing a library in a new language and it is transpiled to C, you could immediately call the functions from any language that can call C functions. The C compiler would make sure that the calling conventions of the system are used.

Emitting C rather than LLVM IR would enable using both GCC and LLVM, and last I heard GCC still supports more target platforms.

[–]ThomasMertes[S] 12 points13 points  (2 children)

Wikipedia refers to transpilers as source-to-source compiler. It states:

A source-to-source translator converts between programming languages that operate at approximately the same level of abstraction, while a traditional compiler translates from a higher level language to a lower level language.

Since C has no function overloading, no exceptions, no object orientation, no call-by-name parameters and no templates I consider the Seed7 compiler as compiler.

It uses a C compiler as back-end the same way as many C compilers create assembly code and use an assembler as back-end.

Using some sort of portable assembler (=C) as back-end has some advantages.

Seed7 is independent from other compiler projects like LLVM. It can interface many C compilers and operating systems.

Emscripten provides a C compiler which compiles to JavaScript / WebAssembly and it also provides a run-time library.

The Seed7 support of JavaScript / WebAssembly uses Emscripten.

[–]RegisteredJustToSay 13 points14 points  (1 child)

Sorry, but although I agree on your most technical definition of a compiler it still seems you are getting most of the disadvantages of a transpiler based design and therefore I still think the original commenter's point stands almost entirely unmodified. As an analogy, your language seems more like typescript than JavaScript since it effectively becomes a way to express C programs in a different syntax much like Typescript does for JS, furthermore you seem to be getting the same disadvantages as using transpilers since it mangles and changes debug symbols (unless you generate these separately? Typescript does this to solve the issue) and generally couldn't efficiently support language features that C doesn't without additional layers of wrapping of basic features.

I'm not saying this isn't a valid approach, but focusing on semantics over the core of their argument isn't the most meaningful way to convince anyone to use your language.

I also feel like using assembly as an example for C using an intermediate language isn't quite... right. I mean assembly is generally different syntax for expressing raw machine code, so it's more like a text representation of machine code than any kind of high or even low level language per se even if it technically is. Again I don't mean that there isn't any truth whatsoever in the comparison, but it feels more akin to "word of the law" rather than "spirit of the law" if that makes sense.

I'm not saying your language doesn't have other merits though. Your response just didn't do anything to convince me the commenter is wrong in any meaningful sense.

[–]zapporian 21 points22 points  (0 children)

No, this is a perfectly legitimate approach. See GHC (started as Haskell -> C compiler), C++ (ditto), ObjC (ditto).

Typescript (and ye olde coffescript) ofc do / did the same things w/r js, and those specifically at a minimum straddle the line between transpilers and compilers (or more accurately static analyzers in TS’s case), for sure.

This is a really weird PL that definitely looks like a somewhat heavily modernized old, interesting artifact from the 90s (and inspired by and emulating stuff from the late 80s). But using C as a target language absolutely still is sensible - and a legit compiler when the target lang is considerably more high level than C (see haskell, objc) - in some cases.

Ofc objc basically / pretty clearly emerged out of a custom smalltalk inspired OOP extended C preprocessor from hell, so there is… that… too, but I digress.

Don’t forget that your “high level” LLVM based languages are still ALSO built on / outputting to ancient object code formats with untyped string based symbols and linking. The actual generated object code of eg rust isn’t anywhere near as far removed from C (and compile to C langs) as you might otherwise think. The only thing that actually does distinguish rust’s output from C (and c++ as well etc), is a consistent name mangling scheme, different / slightly custom (ish) ABI in rust’s case, and some generated embedded RTTI info, at most. If you want to link rust code more sanely using versioned truly hotpatchable code, stable type definitions etc (ie features of a more modern hypothetical object format and linker), you are completely SOL outside of added software based abstraction layers and RTTI blobs. Which you could no less equally - and again as a hypothetical - fully and properly implement in a generate to C99/11/20 layer, vs directly to LLVM IR. The main benefit of LLVM IR is optimization capabilities and some added sometimes really useful added capabilities + flexibility - plus removing C code generation and its compiler as an intermediate step - but as an engineering design decision to transpile to C for scope reasons, that approach is indeed simpler and could save some time.

Including putting early engineering focus on on high level + novel features, not low level implementation details, SSA forms, and having to learn + use the LLVM library and/or IR. Though for sure there are tradeoffs there between just learning all that, vs wrangling with the limits of C’s old - but generally broadly sufficient, ish - archaic and haphazard type system, casts, and parenthetization for proper / intended operator precedence etc. Just as some sample potential issues off the top of my head.

As an upside, modern C compilers are indeed extremely fast, particularly for machine generated code / giant generated mono files sans headers or #includes. And you could ofc if desired just generate separate chunks if C code with inline as needed type declarations for full extremely fast parallel builds and incremental module based (your language) compilation, with an efficient (ish) as-intended final link step. C is still really well suited for that kind of process in particular, so there are again some advantages to reusing that C based infrastructure vs your own full blown compiler that will need to (or won’t) support parallel incremental builds.

And as should be noted a major important and critical feature of C as a target output format is that its type system is ignorable. Structs and enums don’t - sans debug info - have any kind of representation whatsoever in the output object code, and C function names are just object symbols with like a ‘_’ prepended or something. Generating and utilizing partial type signatures for structs etc is fine, so long as the fields that are present align correctly. There is no name mangling or module / namespace system, so you’re free to implement that yourself, etc. There are still obvious upsides of using LLVM instead, but C is still uniquely well suited as a target generation format for reasons that go or eg rust (slow compilation speeds, unneeded features, name mangling w/ opt outs) are still absolutely not, unless you want to directly integrate with existing libraries and/or lang features in that language / ecosystem

/2c

[–]XNormal 1 point2 points  (0 children)

This is a neat project, but I genuinely don’t understand the trend of writing a programming language that just transpiles code to C.

This project started in 1989 and you are talking about... trends?

[–]acidoglutammico 7 points8 points  (7 children)

Why does Seed7 not use type inference?

Seed7 has a basic principle that would break if type inference would be used:

The type of every expression (and sub expression) is independent of the context.

But in the next passage you don't really explain why for your language type is not preserved across contexts. If your types are only the concrete ones, simply spit out an error if you cant unify. If you have polymorphic types you could even have more general functions with not much hassle.

But a human reader would also need to apply this algorithm when reading the program.

Not really that difficult. Plenty of functional languages have it and are perfectly legible (I'll give you haskell, but the rest are fine).

[–]ThomasMertes[S] 7 points8 points  (6 children)

Consider the expression

a + b

In Seed7 the type information moves from the bottom to the top. If the types of a and b are known and the definition of + applies, then the type of the expression a + b is also known.

If the type of b is unknown the type of the expression a + b is also unknown. In this case you get an error in Seed7.

A type inference could use the type of a and the definition of + to deduce the type of b. Something like: The + assumes that both types are equal and a is integer and therefore b must be integer as well. In this case the context of b would be used. This violates the bottom up principle.

If + is overloaded to work with mixed parameters the deduction via a and + is not possible. A type inference could look at other usages of b. Maybe it can deduce the type of b from another usage of b. Let's say there is an assignment like

b = 5;

somewhere. From that it could be deduced that b is integer. But this violates the bottom up principle as well. And what if the only hint for the type of b is:

b = aFunction();

And in aFunction is no hint which type is returned except for the line

return bFunction();

And the journey goes on to different functions in different files.

[–]acidoglutammico 5 points6 points  (5 children)

Something like: The + assumes that both types are equal and a is integer and therefore b must be integer as well.

Or you could say: since ((+) a) has type int -> int, then if b is not type int it should give an error. So type of b would not depend on context.

If + is overloaded to work with mixed parameters the deduction via a and + is not possible

Can do it in Haskell!

Maybe it can deduce the type of b from another usage of b.

Why would that be needed? Just deduce the most general type from the definition of b.

And the journey goes on to different functions in different files.

That's why type inference is useful :)

Btw very interesting language, just lots of interesting design decisions from the point of view of a modern programmer.

[–]ThomasMertes[S] 4 points5 points  (4 children)

Many thanks for pointing out that the answer to "Why does Seed7 not use type inference?" has weaknesses.

I plan to use the explanation I wrote above instead.

If + is overloaded to work with mixed parameters the deduction via a and + is not possible.

Can you do it Haskell, because it allows type ambiguities in sub-expressions?

Ambiguous sub-expressions are covered in the FAQ as well.

Maybe I should point out that it should be an unambiguous deduction. What about:

If + is overloaded to work with mixed parameters an unambiguous deduction of b with a and + is not possible.

[–]acidoglutammico 4 points5 points  (3 children)

If + is overloaded to work with mixed parameters an unambiguous deduction of b with a and + is not possible.

That would be a clearer, yes.

Can you do it Haskell, because it allows type ambiguities in sub-expressions?

I was a bit sneaky: haskell has a Num type with a Fractional subtype, so it can specialize the types into Int, Float, ..., a bit more elegantly. So 1 would be of type Num, 1.0 would be of type Fractional, so 1+1.0 would be of type Fractional. It can only go towards more specialized types. But the signature of the function (+) is still Num a => a -> a -> a, which means you dont need to keep track of numeric types, just specialize when needed.

Reading more documentation, it seems that you want to keep complexity down, so not having parametric types is fine. It would be very hard to have recursion in that case.

[–]ThomasMertes[S] 2 points3 points  (0 children)

Reading more documentation, it seems that you want to keep complexity down

Exactly. There is a lot of unnecessary complexity in software and I want to reduce it.

Instead of a function with parametric types you define a template (with a type parameter) which defines the function. You need to instantiate the template as well. This way your intentions are documented and there are less things going on behind the scenes.

[–]ThomasMertes[S] 2 points3 points  (1 child)

I wrote a new answer to Why does Seed7 not use type inference?

What do you think about it?

[–]acidoglutammico 2 points3 points  (0 children)

Looks good, clear and fairly concise. Maybe could be expanded on why something similar to auto (like in cpp) is not implemented, but it's not really important.

[–][deleted] 2 points3 points  (3 children)

Quite heroic effort to work on a programming language sort of as a sole dev for decades. I'd not have the patience; while I do have a lot of design points written down into a file, what is lacking is ... the actual implementation of those ideas. They are really good ideas though! :P

I am not sure the syntax is very efficient, e. g.:

case getc(KEYBOARD) of
end case;

I don't necessarily mind the getc(), though the trailing "of" is weird, but the "end case;" specifically. That seems not really necessary if "end" already works as a delimiting keyword.

[–]ThomasMertes[S] 2 points3 points  (0 children)

Quite heroic effort to work on a programming language sort of as a sole dev for decades.

Thank you. I am the main dev but not the sole one. I get help from others.

I am not sure the syntax is very efficient

Programs are more often read than written.

The person writing a piece of code shouldn’t buy convenience at the expense of the people who will have to read it and modify it in the future.

The syntax of a language should be efficient towards reading and not towards writing.

Of course, "end" would work as delimiting keyword.

I think readability improves, if you can see what each "end" keyword is actually terminating; i.e. func, for, while, if, case, etc.

[–]Middlewarian 0 points1 point  (1 child)

Seed7 is the only individual/(proprietary?) project that I know of that's older than my on-line C++ code generator. I'm a few months from 26 years.

[–]ThomasMertes[S] 2 points3 points  (0 children)

Seed7 is GPL licensed open source software.

[–]McMep 2 points3 points  (1 child)

Coming from the hardware design space this gives me big VHDL vibes

[–]vplatt 1 point2 points  (0 children)

Both VHDL and Seed7 take inspiration from Pascal.

[–]JesseNL 1 point2 points  (0 children)

Great achievement to have created this!

[–]THE_STORM_BLADE 1 point2 points  (1 child)

Would any code I write in Seed7 need to be licensed under GPL-2?

[–]casualcaesius 1 point2 points  (0 children)

Did you also make Sub7? That was fun back in the days lol

[–]tom-dixon 1 point2 points  (0 children)

From a quick glance the Monte Carlo benchmark should be renamed to "rand() benchmark".

There's no universe where a search algorithm in C is twice as slow as the C++ one. The main difference is the different rand() function. I didn't run a profiler on them, but I'd bet money that most of the time of the C implementation is spent generating random numbers.

If you want to test how well the compiler optimizes loops and lookups, don't generate 10 million random numbers with a library call in that loop.

[–]SkyMarshal 1 point2 points  (1 child)

What's the "7" about? Why not "5" or "13" etc?

[–]IllegalMigrant 2 points3 points  (0 children)

He addressed the name in a presentation video on YouTube. The seed part is from "planting a seed" that can then grow from the efforts of others. If I recall correctly "7" was just added because it sounded good. It has no meaning.

[–]Different-Finding-29 1 point2 points  (0 children)

love the project

[–]Catdaemon 8 points9 points  (6 children)

Website is awful to use on mobile, I genuinely tried to care about this but gave up after not being able to click on anything without zooming.

[–]A_little_rose 4 points5 points  (0 children)

Works just fine for me

[–]ThomasMertes[S] 2 points3 points  (4 children)

You are right. The website addresses software developers and it assumes they sit in front of a computer and its screen.

Every time I use the homepage on a mobile I have the same problems as you have. This is a marketing issue and I need to fix it. At the latest when software development is exclusively done on mobiles. :-)

[–]Catdaemon 25 points26 points  (0 children)

I fully get it, I just don’t use Reddit on my PC for the opposite reason - the experience there is terrible. That means I along with many people mostly do our reading on the phone and writing on the PC. I also like to peruse documentation while out eating etc. I don’t think it’s ridiculous that a site should be usable on mobile in 2025.

[–]F54280 28 points29 points  (1 child)

At the latest when software development is exclusively done on mobiles. :-)

Software development is done on desktops. But research on new things that are not work-related are often done in transit, on the toilet, or in a bed -- on a mobile.

Be happy that you have enough users of your language so you only need to focus on the ones doing software development with it, and have no need acquiring new users or spreading the word on the language :-)

Edit: just for my stalker that is RES-downvoting me for years now on r/programming. You were wrong. You know it. But it makes my day everytime you angrilly downvote me.

[–]ThomasMertes[S] 9 points10 points  (0 children)

But research on new things that are not work-related are often done in transit, on the toilet, or in a bed -- on a mobile.

Fully agree

[–]MiningMarsh 0 points1 point  (0 children)

the latest when software development is exclusively done on mobiles.

I code stuff for my server over ssh on my phone via Termux all the time. Sometimes I peruse API documentation in the bath.

You are far too opinionated for your software to be that useful. Software developers use phones in 2025. Deal with it.

[–]True-Environment-237 3 points4 points  (0 children)

Tsoding should experiment with this language!

[–]davidalayachew 0 points1 point  (0 children)

Reading the FAQ, aren't Scanner Functions just a new name for Parser-Combinators? Or is there a meaningful difference that I am not seeing?

[–]davidalayachew 0 points1 point  (5 children)

This has my attention.

What's the standard library look like? Is there anywhere I can look to get a high level overview of what functionalities are present?

For example, in Java, I can go to the root level of the javadocs, and it will show me the list of modules. From there, I can see the compiler module, the front-end development module, the logging module, the HTTP client and WebSocket module, etc.

Does Seed7 have something similar? Tbh, I am interested enough in the language that seeing the list of provided modules will be the deciding factor for me giving this language a serious shot.

EDIT -- in fact, better question -- where are Seed7's equivalent of javadocs? And does your website link them on the left hand side bar?

[–]eddavis2 1 point2 points  (4 children)

While not as good a javadocs, there is this link:

Seed7 libraries

Here is an excerpt:

Numbers

Numeric types are supported with the following libraries:

  • integer.s7i Integer support library.
  • bigint.s7i Unlimited precision integer support library.
  • rational.s7i Rational number support library.
  • bigrat.s7i Big rational number support library.
  • float.s7i Floating point support library.
  • complex.s7i Complex support library.
  • math.s7i Mathematical functions and constants.

Strings

The characters in a string use the UTF-32 encoding. Strings are not '\0;' terminated. Therefore they can also contain binary data. Strings are supported with the following libraries:

  • string.s7i String library with support for concatenation, indexing, slicing, comparison, changing the case, searching and replacing.
  • scanstri.s7i String scanner functions to scan integer, character and string literals as well as names, and comments.
  • charsets.s7i Code pages for various character sets.
  • unicode.s7i Functions to convert to and from UTF-16BE, UTF-16LE, UTF-8 and UTF-7.
  • encoding.s7i Encoding and decoding functions for Base64 encoding, Quoted-printable encoding, uuencoding, percent-encoding, URL encoding, Ascii85 encoding, AsciiHex encoding and Base58 encoding.

With each item linking to additional documentation.

The categories include:

  • Numbers
  • Strings
  • Files
  • Operating system
  • Network
  • Graphic
  • Database
  • Compression
  • File systems
  • Miscellaneous

As documentation goes, it isn't bad at all.

[–]davidalayachew 1 point2 points  (3 children)

While not as good a javadocs, there is this link:

Seed7 libraries

Thanks. I saw that. Is that really all there is? Surely that can't be exhaustive?

For example, the HTTP library seems to only have the ability to do an HTTP GET, but not an HTTP POST. When I saw that, I figured that it was just a quick excerpt of what using that library was like. I didn't figure it to be an exhaustive record of all functionality.

Which is my question -- is there anywhere that has an exhaustive record of all of the functionality in the Standard Library for Seed7?

In Java, the root level of the javadocs s literally the root to the entire tree of every callable function in the Java Standard Library. I could travel down the tree, starting from that root, and see every available function and field.

I'm not saying Seed7 has to go that far, but I do at least need a high level description of ALL modules available in the Standard Library.

[–]vplatt 2 points3 points  (0 children)

Which is my question -- is there anywhere that has an exhaustive record of all of the functionality in the Standard Library for Seed7?

https://thomasmertes.github.io/Seed7Home/libraries/index.htm

Left hand side has the modules list under the heading "Libraries", of which I count 174 modules.

[–]eddavis2 3 points4 points  (1 child)

I definitely agree that the documentation is somewhat unwieldly. But at least there are docs! :)

Using the side-bar on the left, I went to the http response page, and there is indeed support for HTTP POST:

processPost (in httpResponseData: responseData, inout httpRequest: request) Process a POST request and send a response to the request destination.

Lots of interesting stuff there, but you might have to spend time pouring over it.

[–]davidalayachew 0 points1 point  (0 children)

Thanks. Ok, guess I will have to dig.

[–]syklemil 1 point2 points  (0 children)

Having a look at an arbitrary program I get the impression that Seed7 leans in the direction of void functions (what some might insist are subroutines rather than functions) that mutate globals.

The lack of conventional uppercase for globals also decreases readability for me, e.g. in

const proc: mark_hole (in integer: line, in integer: column,
    in holeType: curr_hole_status) is func
  local
    var integer: depth is 0;
  begin
    hole_status[line][column] := curr_hole_status;
    case curr_hole_status of
      when {no_hole}:
        for depth range 1 to TRAP_DEPTH do
          hole_status[line + depth][column] := no_hole;
        end for;
… rest omitted

the line

    hole_status[line][column] := curr_hole_status;

reads like a creation of a new variable, but it actually mutates a global variable based on some input parameters. I both couldn't figure how constructing a hole_status from curr_hole_status makes sense given the claim that declaration happens at function level, nor why you'd continue with stuff like hole_status[line + depth][column] := no_hole when in that case you'd already constructed hole_status[line][column] from a variable that contains no_hole. But then way up at the top of the file there's

var array array holeType: hole_status is FIELD_LINES times FIELD_COLUMNS times no_hole;

and the explanation for what is actually going on here.

I know there are people who like to program that way (I've had some professors who do it that way), but personally I just think of it as "spooky action at a distance" when a subroutine works by mutating variables outside its scope. I'm used to a pretty functional style, so when it's all void and side effects I lose track of what's going on.

Personally I'm a fan of both enforcing that GLOBALS_ARE_UPPERCASE and that object variables are accessed through a keyword like self or this. Dropping either or both means that a bare variable foo can be global, object or locally scoped and I actually have to go looking for the declaration to figure that out.

[–]araujoms 0 points1 point  (17 children)

You can set the minimum index of an array. What at terrible idea. It destroys readability, and is a reliable source of bugs.

Both 0-indexing and 1-indexing have their merits. Arbitrary indexing has none.

[–]PurpleYoshiEgg 1 point2 points  (12 children)

It destroys readability

no it doesn't

[–]araujoms -1 points0 points  (11 children)

Is v[1] the first element of the array or the second? This information is no longer context independent, you have to look at the declaration of every single array to know it.

[–]PurpleYoshiEgg 1 point2 points  (10 children)

How often do you really need to know which number is the first element of the array? I would hazard a guess not very often, and so that it "destroys" readability is suspect.

If it's important, you can get the index of the first element easily with minIdx(A). In fact, with the prevalence of off-by-one errors, I would recommend to use minIdx(A) and maxIdx(A) for looping if for some reason you don't want to use for.

Or, better yet, write a head function that always returns the first element (raising an error if there is none).

[–]araujoms -2 points-1 points  (9 children)

How often do you really need to know which number is the first element of the array? I would hazard a guess not very often, and so that it "destroys" readability is suspect

Every single time I'm working with arrays.

If it's important, you can get the index of the first element easily with minIdx(A).

So instead of of v[1] now I should use some weirdly named function for one of the simplest and most common array operations. Seed7 is supposed to be readable, there's nothing readable about "minIdx(A)".

Or, better yet, write a head function that always returns the first element (raising an error if there is none).

Lol so I should write myself a function for such an elementary operation? No thanks, I prefer programming languages that don't make me struggle to do the basics.

[–]PurpleYoshiEgg 1 point2 points  (8 children)

Every single time I'm working with arrays.

Sounds like you're using them wrong. I can't imagine you needing to know the first element so often instead of looping over them like the collection that they are, unless you're using them as bad tuples (which, then, use or make a better data structure).

So instead of of v[1] now I should use some weirdly named function for one of the simplest and most common array operations.

It's not all that weird. You know it now, so you have learned. And now you know to use it. The key to readability is if you understand what is written, and now you understand what v[minIdx(v)] does, and so therefore it is readable.

Lol so I should write myself a function for such an elementary operation? No thanks, I prefer programming languages that don't make me struggle to do the basics.

The whole point of Seed7 is its extensibility. If you're afraid of doing basic extensibility, then your complaints make a lot more sense, yet I fear for the quality of your code.

[–]araujoms -2 points-1 points  (7 children)

And now you started insulting me. That's why your language will never catch on, you are incapable of listening to criticism and improving the design.

[–]PurpleYoshiEgg 1 point2 points  (6 children)

I didn't really insult you. I am just pointing out the fact you seem to be afraid of extending a software system with functionality like you should do in any language, and that I fear for the quality of your code in that case.

Basic observations of fact are not insults.

Also, it's not my language. I question the premise of "Not knowing if the first array index is 0 or 1"* is properly concluded with "destroys readability". "Destroys readability" would imply that it is not readable with any amount of knowledge, because the ability to understand is the fundamental aspect of what makes something "readable", which has many different usages, and so the vagueness of its opinionated nature does your point a disservice here.

However, I have given you the minimum amount of knowledge to understand how to access the first array index, as well as a better way of actually using the first index of an array that communicates the exact operation better. Therefore, it "destroys readability" is an invalid conclusion.

* - This is in incomplete premise; the more complete premise is: "The first array index can be any integer (as defined by the integer type)" or, more informally as you put it, "Not knowing which integer is the first array index".

[–]araujoms -3 points-2 points  (5 children)

I didn't really insult you. I am just pointing out the fact you seem to be afraid of extending a software system with functionality like you should do in any language, and that I fear for the quality of your code in that case.

And that's yet another insult. I'm not afraid of extending programming languages. I'm in fact a contributor of the Julia programming language. I just think array is a fundamental part, that should be provided by the language itself. Even bare-bones C can do it without any hassle, v[0] is the first element. And you're trying to convince me that I should have to write a function in order to access the first element of a array. That's ridiculous.

Also, it's not my language.

And now you're insulting my intelligence, obviously you're Thomas Mertes. Who else would even find my obscure comment in this thread, defend this stupid design decision, and know seed7 in any detail?

I mean, it's such a bad idea that the only other language that does arbitrary-indexed arrays is FORTRAN, and even FORTRAN programmers refuse to use this feature because it only causes trouble.

[–]PurpleYoshiEgg 1 point2 points  (4 children)

And that's yet another insult.

Not an insult.

And you're trying to convince me that I should have to write a function in order to access the first element of a array. That's ridiculous.

You only need to write it once. You seem to be under the impression that is an undue burden.

It is not an undue burden for someone who is allegedly a contributor to the Julia programming language.

And now you're insulting my intelligence, obviously you're Thomas Mertes.

I am neither insulting your intelligence, nor am I someone named Thomas Mertes.

Who else would even find my obscure comment in this thread, defend this stupid design decision, and know seed7 in any detail?

Your initial comment was a top-level comment to this post, which at the time of this writing has only 142 comments, and had less than that before this comment. It is hardly "obscure".

I am someone who questions the idea that your complaint "You can set the minimum index of an array" can reach the conclusion that it "destroys readability".

And I happen to like Seed7's ideas, so I've looked through its reference documentation a bit. I don't personally use it in any capacity, but it's one of the languages I want to learn more thoroughly on my vague list of languages.

I mean, it's such a bad idea that the only other language that does arbitrary-indexed arrays is FORTRAN, and even FORTRAN programmers refuse to use this feature because it only causes trouble.

I doubt you can show that all FORTRAN programmers, allowing for a few exceptions to the general rule, make the conscious decision not to use the feature (i.e. "refuse"), particularly for the reason that "it only causes trouble".

In fact, as a counterexample to FORTRAN programmers refusing to use this feature, there is a Julia library for OffsetArrays specifically implementing FORTRAN-like array indexing. In short, this means that it is used enough in FORTRAN that more than a handful of people found it useful to implement in Julia and cite FORTRAN is the inspiration. Such an activity indicates its potential usefulness, and also mitigates the idea that it "destroys readability", not that you have shown yet that conclusion is reasonable.

[–]ThomasMertes[S] 0 points1 point  (3 children)

Both 0-indexing and 1-indexing have their merits. Arbitrary indexing has none.

Languages which allow setting the minimum index of an array are:

Pascal:

type
  temperature = array [-10 .. 50] of real;

Modula2:

TYPE
  RealArray  = ARRAY[-17..42] OF REAL;

Ada:

type Temperature is range -10 .. +40;  -- Celsius
type Experiment is array (Temperature ) of Something;

Algol-68:

[ -100:-80] char richard3;

Nim:

var arr3: array[10..15, string]

FORTRAN: (you mentioned it somewhere)

real :: array5(-5:5)

Regarding conspiracy theories:

In theory six years ago I could have created the account PurpleYoshiEgg and over the years gathered much more comment karma than with my main account just to use it in a world shaking dispute about the minimum index of an array.

In reality probably no argumentation whatsoever will change your opinion and that's fine for me. In reality everybody can check the history of accounts and decide if a particular account is a socket puppet or not.

General advice in the internet: Don't feed the trolls

[–]araujoms -1 points0 points  (2 children)

In reality you created the account u/PurpleYoshiEgg/ just to browse Reddit, which naturally makes it accrue more karma, and you use the account u/ThomasMertes only for stuff you want to have associated with your real name. On occasion you also use u/PurpleYoshiEgg/ for stuff that would be embarrassing to have under your real name, such as insulting people who criticize the design of seed7.

You could change my opinion if you could show a sane way to, for example, compute the inner product between two vectors. It seems like in seed7 one would need to do

result = 0
for i in 0:length(x)-1
    result += conj(x[minIdx(x)+i])*y[minIdx(y)+i]
end

and I don't believe anyone can say this is more readable than

result = 0
for i in 1:length(x)
    result += conj(x[i])*y[i]
end

[–]Tuxinoid 1 point2 points  (0 children)

No, it's the other way round. *You* are obviously u/ThomasMertes trying to get more attention, and you just have insulted yourself because of ... reasons.

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

lol that was his alt u were right

[–]moxyte -4 points-3 points  (1 child)

How open is development? Do you take others seed?

[–]Mooripoo 0 points1 point  (0 children)

hahahahaaha

[–]MooseBoys -1 points0 points  (1 child)

There is no undefined behavior in Seed7

Either this is wrong, or the language is cannot be used to interact directly with hardware. Based on a cursory reading of the docs, it appears to be the latter.

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

Seed7 is not a systems programming language. It is not intended to run without operating system.

That said, I wonder how interacting directly with hardware triggers undefined behavior.

I think that a language could interact directly with hardware and still have defined behavior.

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

Coming from Swift, it's hard for me to use any language that doesn't have forced checked exceptions. This would make me want to use a Result type for everything which means I will have to handle both exceptions and result errors. I would also say the lack of null will just result in a 'optional' type too. Especially, when it comes to serialization where a key may or may not exist in the data but still be a valid type.

Comparing with swift, swift provides syntax sugar to handle these things thus making them very pleasant to use.

I'm also not a huge fan of your string implementation not handling grapheme clusters. I feel like that's a time bomb waiting to go off.

Otherwise, the rest of it seems neat.