SoulofDeity Posted May 15, 2015 Share Posted May 15, 2015 I had originally began tinkering with this idea in a separate thread about a new interchange format, but finally decided it was deserving of it's own thread. The vision for Ergo is a statically typed compiled language for systems programming focused on abstraction, expressiveness, and simplicity. It's goals include high polymorphism, high code reusability, high readability, and a low learning curve. Let The 'let' keyword is used to declare new local variables. let x = 5 x = 6 The Ergo programming language also supports tuple assigment like in Python. let x, y = 5, 5 x, y = 6, 6 The types of the variables are deduced from the values assigned to them upon declaration. And The 'and' keyword is used to combine statements. something () and something () Do The 'do' keyword is used to execute instructions. When immediately followed by an instruction, it executes that instruction. do something () this can also be used in conjunction with the 'and' keyword. do something () and something () If a newline immediately follows a 'do' keyword, then it executes a block of instructions with the same indentation level. do something () something () For (+ 'from', 'to', 'by', 'in') The 'for' keyword is the most powerful keyword in the Ergo language. It doesn't create loops, it declares the subject for a specific context. The most recognizable form is the iteration context. for i from 0 to 5 by 1 do something () for j, k to 10, 10 do something () Here, the subjects are 'i', 'j', and 'k'. The above code is equivalent to the following C++ code: for (int i = 0; i < 5; i++) something (); for (int i = 0, j = 0; i < 10 && j < 10; i++, j++) something (); Iterations have the format 'from <start> to <stop> by <step>'. The start and step are optional though. By default, the start will be 0 and the step will be 1. The next most recognizable form is the enumeration. for e in c do something () Enumerations have the format 'in <collection>'. This is equivalent to the following C# code: foreach (int e in c) something (); The best way to visualize the 'for' statements in Ergo is to break them into 3 parts: for i to 5 do something () for i # part 1 - the subject is 'i' to 5 # part 2 - the context is the range from 0 to 5 by 1 do something () # part 3 - the predicate As you can see here, the loop is created by the context. In the future, if anything aside from an iteration or enumeration is used as a context, the predicate will only be executed once. Next The 'next' keyword is used to advance the iterator or enumerator in a context. It's equivalent to the 'continue' keyword in C. for i to 5 do next Exit The 'exit' keyword is used to escape from a context. It's equivalent to the 'break' keyword in C. for i to 5 do exit On (+ or) The 'on' keyword is used to create an event handler. on print do something () Events may optionally have parameters. on print format, text do something () In the above code, both 'format' and 'text' are parameters. Despite their appearance, they are statically typed. The way this works is simple: they're generic. It's equivalent to saying the following in C++: template <typename T, typename U> void print (T format, U text) { something (); } When some code tries to trigger the event: print ("%s", "Hello") The compiler says, "oh, I see you're passing strings for the format and text, so the print event handler must accept those types". If you're an experienced programmer, just take a minute to let that one sink in. Unlike most other languages, Ergo actually assumes that you know what you're doing and generates code for whatever object types you trigger the event with. If you wanted to pass numbers instead, Ergo is perfectly okay with that and you don't have to change a single line of code. This extreme level of polymorphism and simplicity makes Ergo easy to use for prototyping. However, it has to know ahead of time which types to generate code for. For this, the 'for' keyword is used to specify a set of type constraints. on add x, y for int, int or float, float do something () These constraints ensure that only code for ints and floats will be generated. So, if you were to call 'add' with doubles, it'll implicitly cast to float instead of generating an add event handler for doubles. If The 'if' keyword is used to conditionally execute instructions. if true do something () any statement checked with 'if' will be implicitly casted to a boolean. In other words, null or 0 is false and if it's not false it's true. To As shown in the prior example of an iteration context, the 'to' keyword creates a branch from one value to another. This is not limited to integers though. Ergo allows you to exploit this fact to chain multiple conditions together. if x < 5 do something () to x > 5 do something () This construct is a 'to-do list'. If 'x' is less than 5, then the result will evaluate to true. This means that the next statement is implicitly 'true to x > 5'. Note that the range specifies an iteration context, so regardless of if 'x' is greater than 5 or not, the predicate will not be executed. In laymens terms: if 'x' is less than 5, then all the proceding 'to' statements are ignored. However, if 'x' is not less than 5, then the result will evaluate to false. This means that the next statement is implicitly 'false to x > 5'. In this case, if 'x' is equal to 5 then this becomes 'false to false', and thus the result remains false; passing on to the next 'to' statement. If 'x' is greater than 5, then this becomes 'false to true', causing the predicate to be executed once and the result to be changed to true; meaning all proceding 'to' statements will be ignored. In laymens terms, the 'to' keyword + boolean values = else if. Note that this isn't limited to if statements. The following is a syntactically correct fake if-statement in Ergo false to x < 5 do something () One way of looking at this is that there is no such thing as an if statement, only 'to' statements; and 'if' is just an alias for 'false to'. You can also use it to execute a piece of code a specific number of times. 0 to 5 do something () Else The 'else' keyword is used to conditionally execute instructions if the prior statement wasn't executed. The most common use of this is with 'if' statements. if true do something () else do something () For this usage, a 'do' statement must come immediately after the 'else' keyword. if x < y do something () to x > y do something () else do something () -------------------- Up For Consideration I'm considering the use of 'select' queries like in C#. The type system is also in need of work, C-types just look out of place in Ergo imo. 3 Link to comment Share on other sites More sharing options...
SoulofDeity Posted May 17, 2015 Author Share Posted May 17, 2015 Some changes have been made. Firstly, the 'let' keyword is no longer used to create local variables. Instead, it's used to define locally-scoped macros. For constants, the format of a let-statement is 'let var be something'. For example: let x be 5 For functions, the format of a let-statement is 'let fn arg1, arg2... do something'. For example: let print format, text do something () This new syntax makes it unneccessary for there to be an 'on' keyword. Another change I've been working on is an alternative wordless syntax. and && as : be := else ..1b from = if 0b.. let \ or || then \ to .. An example usage of this, the following code: let print format, text do let x be 5 if format != null and text != null then do something () else do something () for i as Integer from 0 to 100 do something () could be alternatively be written as: \ print format, text do \ x := 5 0b.. format != null && text != null \ do something () ..1b do something () for i : Integer = 0 .. 100 do something () or print format, text do x := 5 0b.. format != null && text != null do something () ..1b do something () for i : Integer = 0 .. 100 do something () I'm hoping to make it possible for all keywords to have symbolic alternatives. Another thing I've been working on is the type system. The suffixes 'b' and 'i' appended to numbers stand for 'boolean' and 'integer'. Instead of the traditional 'float' or 'double' types, I've been pondering the use of a type called 'scalar'. The meaning of the word is unambiguous between computing and mathematics as a representation of a real number which may be a field of a vector or matrix. If I did this, it'd also make sense for Ergo to natively support vector operations. A feature like that could allow Ergo to outperform C or C++ in many situations. EDIT: Thinking about the new syntax a little bit. With consideration for the symbolized form, a better format may be: let print for format, text as String, String do something The 'as String, String' part is an optional type constraint. One benefit of this syntax is continuity. Functions are pretty much the same thing as loops. 'for <input> do <something>' 2 Link to comment Share on other sites More sharing options...
SoulofDeity Posted May 18, 2015 Author Share Posted May 18, 2015 Some more things to mention. As said in the previous post, the 'let' keyword is equivalent to '\', technically making it optional. Well, in Ergo, sets are delimited by commas and may optionally be encapsulated by parentheses. So let print format, text do something is equivalent to print (format, text) do something () This is actually a lot like the B programming language. print (format, text) { something () } The only differences are that Ergo doesn't use brackets and that everything is generic by default, where everything in B is a 'word' (the optimal 'int' for the device being programmed for). One idea I was thinking about was to have Ergo bring back ANSI-C style type constraints. To the less informed, this is how C used to look... print (format, text) char *format; char *text; { } What I like about this the most is the separation between the definition of the function and the 'contract'. While it may seem wordy, it's also extensible. Instead of types, you could have 'concepts'; which are a set of assertions about which attributes, operators, and functions that an object has. In this case, we are saying that 'format' must match the concept of a 'char *'. This was actually a highly anticipated template metaprogramming feature which had been planned to be introduced into the C++ standard a while back but was pulled out at the last moment because the standardization committee didn't feel it was mature enough. Before I implement it in Ergo though, I want to tinker with the syntax a bit to make it more understandable. 1 Link to comment Share on other sites More sharing options...
SoulofDeity Posted May 22, 2015 Author Share Posted May 22, 2015 Working on the shorthand syntax a bit. and && be := do => let \ minus - on \ or || plus + times * to .. :: # used for delimiting namespaces Here's a short preview of what function declaration looks like: System::Console::Print := argc, argv => x := 5 Print := System::Console::Print This is equivalent to on System::Console::Print := argc, argv do let x be 5 let Print be System::Console::Print For the sake of clarity, I decided not to kill off the 'on' keyword in favor of 'let'. Since I'm using the ':=' operator to assign by reference, I thought that the C++ style of 'System::Console::Print' looked more suitable than 'System.Console.Print'. For the 'do' keyword, I chose '=>' because it's used for lambda in C#. I had thought about incorporating ternary if statements like: if \ then ? else : So you could do: \(x < 5)? => something :(x > 5)? => something : => something In place of if x < 5 then do something else if x > 5 then do something else do something Note that the '\' is optional; I only used it because I'm an obsessive compulsive douchbag turned on by symmetry. Anyhow, if I go this route, then I can't use the ':' symbol for 'as'. Although, if I excluded the 'as', then ':' could also be used for the 'in' keyword like: for e : c => something This may actually be for the best considering that the traditional syntax for 'let' as 'let x be y in z' would simply be 'x := y : z'. 1 Link to comment Share on other sites More sharing options...
SoulofDeity Posted May 24, 2015 Author Share Posted May 24, 2015 As seen above, I've been advocating duck-typing and a sort of parametric/ad hoc hybrid polymorphism. I think I'm going to do away with the ad hoc part and add a 'use' keyword paired with a set of types. The idea being that you can do something like: use add integer, integer add := x, y => x + y Before I had this idea, unless you added a set of type-constraints, code would only be generated if you used it on those types. Here, I'm doing away with type-constraints and allowing the programmer to explicitly declare the use of types to generate code for. Note that you'd only need to do this to generate code for unused types, eg. If you call 'add(5, 5)' somewhere in your code, it'll see that you're using a type signature of 'integer, integer' and behave as though you've added 'use add integer, integer' to your code. For this reason, the 'use' keyword is only necessary when creating libraries; and in any other situation, simple parametric polymorphism is fine. Old Notes... Another thing I should mention; I had stated this before, but the ':=' operator is for assignment by reference. Assignment by value uses the '=' operator. So x = 5i y := x Is equivalent to the following C code: int x = 5; int restrict *y = &x; One of the benefits of this is the added safety. You can't accidentally assign a value to a pointer or reference a reference. It also means that x := y => y + 5 is completely different from x = y => y + 5 In that the first stub of code assigns a reference of the lambda to 'x' so you can call 'x' like a function, and the second stub assigns the value of the evaluated lambda to 'x' (x = y + 5). Since I'm no longer using ':=' to mean 'local variable', I'll need to think of a way to handle that. That said, I don't want to just get rid of pointers altogether because they're a powerful feature. But at the same time, I don't want to make everything as complex as C and C++. EDIT: An idea I just had, 'x()' could be interpreted as, "value of x". In this sense, the '(' and ')' could be used for both arrays and functions. Then, perhaps '{ }' could be used for references. eg. x = (1, 2, 3) x(0) // value is '1' (*x) x(1) // value is '2' (*(x+1)) x{0} // pointer to 'x' (&x) x{1} // pointer to 'x + 1' ((&x)+1) print(x(1)) It's like inside-out C pointers. Link to comment Share on other sites More sharing options...
Recommended Posts