You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I will maintain in this OP a summary of current proposed syntax as I understand it to be. Note this is not authoritative, subject to change, and it may be inaccurate. Please make comments to discuss.
: Type is always optional.
Sum, Recursive, Record, and Product parametrized data type with optional Record member names and optional Record (e.g. Cons) and Product (e.g. MetaData) names:
someCallback(x:Type y:Type(:Type) => x + y) // also ():Type => x + y
var f = x:Type y:Type(:Type) => x + y // not named, also ():Type => x + y
f = x:Type y:Type(:Type) => x + y // not named, also ():Type => x + y
let f(x:Type, y:Type):Type => x + y // named f, also f():Type => x + y
let parametrized(x: A, y: B):A|B => x || y
let parametrizedWhere(x: A, y: B):A|B where ... => x || y
Note that iterator types can be specified for the return value to return a lazy list as a generalized way of implementing generators. The optional (:Type) is necessited for generator functions. Note the (x: Type y: Type): Type => x + y form is unavailable.
I am not sure I like pluggable for a type-class. If it's going to have that many letters either interface or typeclass would be better.
interface List<A>
I am not sure we want to use | for both sum types and union types.
I prefer having 'implementation' before the type-class having the type class first for implements seems inconsistent to me. I also prefer to treat all type class parameters equally. The first is not special so why give it special syntax.
implement List<A>
I am not sure why you put types in the function call syntax? I don't think you need or want them, you only want typed in function definitions.
I don't like that the method syntax is different from the function definition syntax. I think we should have a unified record/struct syntax. If we have:
data List<A> = List(
append : (l1 : A, l2 : A) : A
)
data MyList
let mylist : List<MyList> = List(
append : (l1 : MyList, l2 : MyList) : MyList =>
... definition ...
)
A record above is like a type-class but you can pass it as a first class value.
If we can 'promote' this to implicit use, we can have a single unified definition syntax. Maybe:
let list3 = mylist.append(list1, list2) // use explicitly
use mylist // make mylist implicit
let list6 = append(list4, list5) // use implicitly
I am not sure I like pluggable for a type-class. If it's going to have that many letters either interface or typeclass would be better.
Can't be interface because it would be confused with the way interface works in many other OOP languages. To me as a n00b, typeclass means class, so more misunderstandings. pluggablehas some meaning to a n00b such as myself. Sorry I am not an academic and they are only something like 0.01 - 0.1% of the population.
I am not sure we want to use | for both sum types and union types.
Why not? Sum types are an "or" relationship. Unions are an "or" relationship.
I prefer having 'implementation' before the type-class having the type class first for implements seems inconsistent to me.
Inconsistent with what? implementation Thing Nil or implementation Nil Thing are not sentences and it is not clear which one is which. Nil implements Thing is a sentence and very clear which is the typeclass.
I am not sure why you put types in the function call syntax?
This is ambiguous... is it calling someCallback with x as the first parameter and y => x + y as the second?
Good catch. I missed that one. It indeed conflicts with comma delimited groups in general, not just function calls. I will remove after sleep.
You didn't point out that problem to me when I suggested it. Remember I was trying to make the inline syntax shorter, to avoid the _ + __ shorthand problems.
Edit: there is another option (again :Type are optional):
someCallback(x:Type y:Type => x + y)
But that is still NFG! Because it is LL(inf) because without the leading ( it must backtrack from the space, unless we require Type to be a single token in that context (i.e. use type if need to define complex type as one token).
@keean the only point was to have an optional shorthand syntax (instead of the inconsistent semantics of _ + _ or the obfuscating _ + __) for inline functions and to get rid of those gaudy juxtaposed parenthesis someCallback((....
Thus we don't need : Type for the shorthand syntax, so I propose the following optional shorthand syntax which eliminates the LL(inf) problem as well:
someCallback(x y => x + y)
Which is shorter than and removes the garish symbol soup (( from:
someCallback((x,y) => x + y)
That being generally useful shorthand, enables your request for an optional syntax in the special case of single argument (which I was against because it was only for that one special case):
someCallback(x => x + x)
Instead of:
someCallback((x) => x + x)
However, it isn't that much shorter and the reduction in symbol soup isn't so drastic, so I am wondering if it is violating the guiding principle that I promoted?
Short inline functions might be frequent? If yes, then I vote for having the shorthand alternative since it it would be the only way to write a more concise and less garish inline function in general for a frequent use case. Otherwise I vote against.
Are we optimising too soon? I have implemented the basic function parser for the standard syntax, is that good enough for now? I think maybe we should try writing some programs before coming back to optimise the notation. I would suggest sticking to "only one way to do things" for now, because that means there is only one parser for function definitions, which will keep the implementation simpler for now. What do you think?
Thanks for reminding me about when I reminded you about when you reminded others to not do premature optimization.
I agree with not including the shorthand for now. Then we can later decide if we really benefit from it. I'll leave it in the syntax summary with a footnote.
Are we sure having keywords let and var is the right way to go? If we have keywords for these we might want to have a keyword for functions? I quite like Rust's fn for introducing function definitions?
Are we sure having keywords let and var is the right way to go? If we have keywords for these we might want to have a keyword for functions? I quite like Rust's fn for introducing function definitions?
Instead I have proposed unified functions around let and var.
What would be the alternative to not having let and var? I can't think of one that makes any sense. How would you differentiate re-assignment from initialization? Remember we already decided we can't prefix the type for reference initialization, because types are optionally inferred.
I think structs/objects would probably start with an upper case letter.
Agreed.
My suggestions on types of name tokens for the lexer to produce:
data and pluggable (or typeclass?): [A-Z]+[a-z][a-zA-Z]*
(start uppercase, at least one lowercase, only alphabet)
named function references: [a-z][a-zA-Z]*
non-function and unnamed function references: [_a-z]+
type parameters: [A-Z]+
The exclusivity for type parameters for all uppercase is so they don't have to be declared with <A,B...>.
Edit: the distinction between named functions and non-functions references will be useful, because unnamed functions references should be rarer. However, I was incorrectly thinking that it wouldn't make any sense to give function naming to unnamed function references (which have re-assignable references) because the reference would indicate it is for a function but I had the incorrect thinking the reference could be reassigned a non-function type (but reference types can never change after initial assignment). So I think it would be safe to change the above to:
named and unnamed function references: [a-z][a-zA-Z]*
non-function references: [_a-z]+
The other advantage of that is the lexer can tell the parser to expect a function, which is more efficient and provides two-factor error checking.
Note the compiler must check that the inferred type of the reference matches the function versus non-function token for the name.
Personally I don't want one module per file. I have done that in Java, and I didn't like it. You can end up with a directory full of very small files, which makes the code hard to read.. A module is about data hiding and API as well as separate compilation.
I would also not require asynchronous loading, it tends to make applications very slow, and people only tend to use it in development preferring to use tree-shaking tools like rollup to put everything in a single file for production (and minifying it too). I don't even use dynamic loading in development because it makes page load very slow.
Personally I don't want one module per file. I have done that in Java, and I didn't like it. You can end up with a directory full of very small files, which makes the code hard to read.
We already discussed your disagreement on this issue. Java’s case was more egregious because as you pointed out that Java requires the filename named according to the class thus forcing a proliferation of files, and I have not proposed such a limitation.
I understand that if modules will be quite small then a proliferation of small files means more files to open instead of just scrolling down. But in theory an IDE can solve this problem and offering to scroll vertically across multiple modules making them appear as though they are in one file. Did no Java IDE ever offer such an improvement? (if not, what has happened to our profession, that programmers are not able to meet the needs of huge ecosystem markets!)
Adding multiple modules within files requires adding a module keyword and a indented-block, and complicates the import syntax. Additionally if require that modules are not qualified by file name, then modules can be moved to different files and repositories which afaics will further complicated matters on many levels (including version control coherence).
A module is about data hiding and API as well as separate compilation.
And thus I see no valid reason to have more than one in the same file. A module is a unit of modularity, and thus separating modular items into files makes for clean repository and version control system.
One module per file is compatible with JavaScript and TypeScript. The least amount of unnecessary discord with the target output language is very desirable.
PL design is about extreme prioritization and leaving as much as possible out of the language and not including every little nuanced option we might want to throw in the kitchen sink.
You recently stated that you are coding a lot in TypeScript. How are you coping with this “major downgrade” to one module per file?
I would also not require asynchronous loading, it tends to make applications very slow
Code should be loaded once and then run many times over the course of application, thus any overhead of a Promise is irrelevant. If the code is on the local disk, then latency and load time should be rather insignificant. I think the overhead you must be referring to is the HTTP overhead of loading many small files over the wire (or even perhaps local file system latency for accessing many small files)? In that case, the compiler can take care of optimizing by merging small files as you noted (and which I was aware of and already contemplated in my design) and handling this automatically, but the syntax of the programming language need (and should) not be specialized:
preferring to use tree-shaking tools like rollup to put everything in a single file for production (and minifying it too)
We do not want specialization in the source code. K.I.S.S. principle (do not eat the complexity budget). I think you are putting design in the wrong abstraction layer:
I don't even use dynamic loading in development because it makes page load very slow.
You recently stated that you are coding a lot in TypeScript. How are you coping with this “major downgrade” to one module per file?
Typescript allows multiple namespaces in the same file. It all depends on what you specifically mean by a "module".
Code should be loaded once and then run many times over the course of application, thus any overhead of a Promise is irrelevant.
Local disc latency is still a problem. You should have a play with dynamic loading tools like SystemJS, obviously it has to load over HTTP, how else can JavaScript load anything (unless you are talking about running in Node.js rather than in the browser, then you can output 'require' statements and have dynamic loading, but it won't work in the browser).
We do not want specialization in the source code. K.I.S.S. principle (do not eat the complexity budget). I think you are putting design in the wrong abstraction layer:
If you compile to TypeScript, then you can use the output options to choose CommonJS, ES6, SystemJS for loading, so it can work in Node or in the browser with dynamic loading or bundled. You would just use the normal TypeScript 'import' statements.
I know sometimes perhaps we need to express privacy access inter-module, so if we supported that feature then we might like to have more than one module in a file (even though it still would not be absolutely necessary). But I did not yet propose offering that feature and probably want to avoid it unless we really need it.
If you compile to TypeScript, then you can use the output options to choose CommonJS, ES6, SystemJS for loading, so it can work in Node or in the browser with dynamic loading or bundled. You would just use the normal TypeScript 'import' statements.
Are you just stating that to document in detail your understanding of what I wrote I would do? Or is there some other reason you mention the details of my solution?
The compiler will optimize it. If I have to fork TypeScript, then so be it. But hopefully I can work within the feature set of TypeScript.
Are you just stating that to document in detail your understanding of what I wrote I would do?
You wrote you wanted to use promises to dynamically load modules. I stated you were focusing on the wrong abstraction layer and should just output static import statements and let typescript handle it (or implement the same kind of compiler options as typescript if going straight to JavaScript).
Edit: I think I see what you mean, you want to represent module loading as a promise in the source language, not the target language. This is problematic because it means modules must be first-class entities. This means that all module dependencies and relationships need to be expressible in the core type system so that modules a typeable value. This is possible, and is something I have been looking at doing, but it requires careful selection and combining of type system features to make sure the type system is sound.
I believe programs should be readable, and that means statically readable, like when printed on paper. What makes programming languages more powerful than graphical application builders is we can use the language centres of our brains to process them. To facilitate that we must be able to navigate a program like we find sections in a book. This means a static layout, not dynamic and changing. Predictability is important, because we can auto-pilot things that are predictable, and it causes minimal cognitive load.
I was thinking about this also before I read your message. I then thought for those who want these groupings of modules, we could offer a grouping keyword and they could place this in a separate file. A smart editor would know when loading the said file, to also load the referenced modules as a linear text view. With this separation-of-concerns, we K.I.S.S. principle the base format for modules keeping them coherent with EMCAScript modules (one per file) and prevent clutter of module keyword and block-indents in module files. Yet we can also then orthogonally meet the need of those who want a static grouped view of related modules. And this interops better with version control in that the grouping changsets are orthogonal to the module changesets.
Win-win design via separation-of-concerns.
I understand it looses the simplicity of being able to see the linear text grouping with any unspecialized text editor (e.g. Notepad). But it does not loose the power of reasoning about language orthogonal to GUIs. And if force everything to be literally copied in text, this is less powerful than structure by named reference, e.g. we would prefer import by named reference and not by copying the entire text of an external module into the top of every module that references a module.
Are you just stating that to document in detail your understanding of what I wrote I would do?
You wrote you wanted to use promises to dynamically load modules. I stated you were focusing on the wrong abstraction layer and should just output static import statements and let typescript handle it (or implement the same kind of compiler options as typescript if going straight to JavaScript).
Edit: I think I see what you mean, you want to represent module loading as a promise in the source language, not the target language. This is problematic because it means modules must be first-class entities. This means that all module dependencies and relationships need to be expressible in the core type system so that modules a typeable value. This is possible, and is something I have been looking at doing, but it requires careful selection and combining of type system features to make sure the type system is sound.
I understand maybe I did not sufficiently explain my proposal.
Copying TypeScript’s method where all imported typings from the module are resolved statically and if none of the runtime code of the module is accessed then nothing is loaded at runtime. Any runtime code (and state) is loaded via an asynchronous model before it can be accessed (either via an explicit assignment of the import to a reference in our language's source code or otherwise an implicit such reference which appears in the transpiled output code). And runtime loading must be optimized (e.g. for some target environments, the compiler might even have everything statically loaded or load modules in large amalgamated chunks).
I do not understand why you think this introduces type soundness issues? TypeScript does not seem to think so? Perhaps you are thinking that the types in the module would first-class? I did not intend to propose that. I am proposing that only the object of (non-private) runtime objects (e.g. the constructor functions and other functions and any state) is first-class; and its type is determined by the items imported. The types are statically resolved. Does that clear up the issue? Perhaps you have a more complex model of module dependencies in mind? Essentially my proposal is just runtime linking and afaics should not have any impact on the type system.
It doesn't necessarily have anything to do with files.
Why have files? Let’s just put the entire program in one file. Or better yet, let’s put all programs that were ever created and will ever be created in one file. Obviously modularity has nothing to do with files.
I think the more salient question is not whether we need more than one module per file, rather whether we need more than one file per module? I am approaching modules conceptually as units of reusable repositories in an open source ecosystem similar to Rust Crates or npmjs.
See 'ML modules'.
ML must be one of the most popular languages. Why are we creating a language. There is already an OCaML to JavaScript transpiler. I believe we discussed that typeclasses can be simulated with OCaML’s features. One of the reasons I provided was because we would lose integration with JavaScript such as Promises and the concepts and syntax are too foreign. I basically wanted to home in on a very elegant K.I.S.S. design that would not be too obtuse for a mainstream, popular language. 80/20 prioritization.
So instead of appealing to authority of ML, could you instead provide examples of which features where our modules will require multiple modules per file or multiple files per module? Because I am not really healthy enough to expend the effort to research ML modules and try to figure out myself which uses cases dictate such.
Edit: modularity-of-encapsulation might not be equivalent to modularity-of-reuse. A module-of-reuse might need more than one module-of-encapsulation, i.e. that each of modules of encapsulation is incomplete without each other. Yet this is expressed by typing and the consumer of the module-of-reuse has to import all the required modules of encapsulation. Yet maybe we wish to be able to import all as a grouping. This is easy to do. A module can import other modules, which satisfies my prior suggestion for a way to group modules. As I previously suggested, a smart editor can offer an option to display multiple grouped modules in a single vertical scrolling amalgam.
The reason to put each module-of-encapsulation in a separate file is so that it can be referenced orthogonally by another module-of-reuse, which reuses only some of the modules of encapsulation. K.I.S.S.. rather than add complexity. If a lot of small files makes it difficult to see the modules of reuse that group these small modules of encapsulation, then put them in a separate directory (folder) in the file system.
This is LL(k=2) thus far. Only 1 token of lookahead. I will update this as I progress to add the rest of the grammar. Eventually this will be added to a new Zer0 repository.
/*
This file can be checked with shell command: ./slk -Ga -k=2 grammar
The lexer MUST:
• Not allow tab characters.
• Never issue the '<' token if it’s preceded by whitespace, because `<` (i.e. ' < ') is a distinct token for the “less than” operator to distinguish it from the use of the '<' token in other contexts in a context-free grammar.
This resolves the LL(∞) ambiguity between the the “less than” operator and the `type-params` production. This is preferrable than wasting the `[` and `]` square bracket pair for type parameters. This also has the benefit of preventing the “less than” operator without whitespace, e.g. `1 < 2` is preferred instead of `1<2`.
For consistency, never issue the '>' token if it’s preceded by whitespace, because `>` (i.e. ' > ') is a distinct token for the “greater than” operator to distinguish it from the use of the '>' token in other contexts in a context-free grammar. For further consistency, never issue the '<' token if it has trailing whitespace, so that formatting is consistent with the '>' token not allowing leading whitespace. However, do send the '>' token when it has trailing whitespace.
For consistency, never issue the '(' token if it has trailing whitespace nor the ')' token if it’s preceded by whitespace. It’s extraneous whitespace anyway.
• Discard blank lines (including those with comments).
• Error if line contains only horizontal whitespace.
• Error if empty line (without a comment) follows INDENT or line(s) containing only comments that followed INDENT.
• Issue INDENT and OUTDENT tokens only for columns at multiple of 3 spaces, e.g. 6 spaces indenting issues two consecutive INDENT tokens.
• Not issue two consecutive INDENT that didn’t have an intervening CR because these are line continuations.
• Issue OUTDENT tokens for all in the INDENT stack before EOF.
• Issue one CR token before every token that starts a line and is positioned at the column of the current INDENT stack.
• Error if the token that starts a line is before the column of the current INDENT stack (and this is in the context of any OUTDENT(s) that have been issued for that line).
• Not require the token to be enclosed in spaces if it’s written in the grammar below enclosed in single quotes instead of backticks.
• The comma token ',' must have trailing whitespace followed by non-whitespace token (other than CR) or end the line (i.e. followed by CR).
*/
module:
{ CR member } EOF // TODO: Optionally parameterised modules may require more than just prefixed with “[ pid-list ]”: https://github.com/keean/zenscript/issues/39#issuecomment-412850869
member:
data
export // Separately list exports instead annotating them in declarations. This reduces clutter on declarations and makes the export attribute a separate concern. Some may protest that they forget to check two places in a file. Uncluttered readability is a higher priority.
value
variable
data: // Not supporting typeclass interface bounds (aka `requires`) unless someone explains why associated type synonyms can’t suffice without associated data types: https://github.com/keean/zenscript/issues/8#issuecomment-412343352
'data' TID [ pid-list ] [ struct-or-disjoint-union ]
struct-or-disjoint-union:
input-list type
INDENT CR struct { CR struct }+ OUTDENT // Disjoint union. I can’t think of any valid reason to have type bounds on the optional PIDs (constructor functions can be employed for that purpose).
struct:
TID [ input-list type ] // If `type` has an output (aka return) type then it’s the type of a variant of the disjoint union which is a GADT:
// http://lambda-the-ultimate.org/node/1134#comment-44653
// https://en.wikipedia.org/wiki/Generalized_algebraic_data_type#Overview
// Apparently GADTs are closed analogous to closed type families and thus not anti-modular: https://github.com/keean/zenscript/issues/8#issuecomment-410219702
// Apparently though it’s associated types combined with existential quantification (not open associated type families) that cause anti-modularity: https://github.com/keean/zenscript/issues/8#issuecomment-410513143
// And existential quantification may be less useful in many cases than union types (unions work with multi-parameter typeclasses and open to new typeclass bounds): https://github.com/keean/zenscript/issues/8#issuecomment-412332373
// And because we can also employ explicit cast subsumption and supersumption of union types: https://github.com/keean/zenscript/issues/8#issuecomment-410894784 (c.f. bottom of post)
input-list:
'(' ID [ `=` prefix4 ] { ',' ID [ `=` prefix4 ] } ')'
pid-list:
'<' PID { ',' PID } '>' // PID is a type parameter: [A-Z]+
export:
'export' instance-or-type { ',' instance-or-type }
instance-or-type:
ID // ID is for values and variables including functions: (?:[_a-z][_a-zA-Z0-9]*|[αβγδεζηθικλμνξοπρςτυφχψωℎℏ𝑒])(?:'*|′*)
TID [ members ] // TID is for data types and typeclasses: [A-Z](?:_[A-Z])?(?:[a-zA-Z0-9]|[A-Z]_[A-Z])*[a-z0-9]
// Since IDs/TIDs can’t begin with upper/lowercase respectively, an exported ID/non-exported TID can be compiled with the prefix “G”/“g” respectively in Go: https://tour.golang.org/basics/3
members:
'.*' // Export all member instances or constructors of the disjoint union with all their members.
'(' instance-or-type { ',' instance-or-type } ')' // Export specific member instances or constructors of the disjoint union.
statement:
'break' [ 'if' infix14 [ 'else' expression ] ]
'loop' [ 'if' infix14 [ 'else' expression ] ]
'for' [ for-variant ] INDENT block OUTDENT
'return' expression // Shouldn’t be employed on the last expression of a function, so it’s consistent with not being used as last expression in the block of a conditional expression. It’s for short-circuiting to avoid writing a more complex conditional expression.
expression
value
variable
for-variant: // https://github.com/keean/zenscript/issues/11#issuecomment-402863461
expression
';' [ expression ] ';' expression
ID { ',' ID } in-or-opt-cond
in-or-opt-cond:
'in' expression [ ':' type ]
`:=` expression [ ':' type ] ';' [ expression ] ';' [ expression ]
block:
{ CR statement }+
value: // Prevents re-assignment to LHS after construction. This is analogous to `const` in JavaScript, but not `const` in C++. It’s not the writable attribute the object assigned from the RHS— which is instead encoded in the `type`.
ID `::=` typed-expr // TODO: if ever we want to construct multiple ID values by destructuring the assignment to a `tuple` of ID, then move “ID `::=` typed-expr” to `expr-suffix` and compiler must parse for correct LHS.
TID `::=` function // Replaces the default type constructor and is only allowed in same module as declaration of the type.
function:
infix14 [ func-suffix ]
variable:
ID { ',' ID } `:=` typed-expr // Optionally construct multiple ID variables with the same value. For destructuring a tuple, the TODO for `value` applies.
typed-expr:
expression [ type ] // Compiler must check for syntax error if redundant type on function declaration expression. Type can also be an explicit cast which is required for subsumption and supersumption: https://github.com/keean/zenscript/issues/8#issuecomment-410499037
expression:
infix14 [ expr-suffix ]
expr-suffix:
`=` infix14 // Compiler must check that the LHS is an expression that can receive an assignment.
[ 'loop' ] 'if' infix14 [ 'else' expression ] // Optional 'loop' here is for a single-expression, do-while loop.
func-suffix
func-suffix:
`=>` function-decl // Pure function
`->` function-decl // Impure function, aka a procedure
// An additional optional short-hand function declaration is the interpretation of an expression: https://github.com/keean/zenscript/issues/6#issuecomment-248631325
// For the function declarations above, the compiler must check that the LHS is an equivalent to an `input-list`.
// The alternative (in the first version of this grammar) to structure the grammar more strictly (to only allow a tuple) was to employ some brackets other than parentheses for the LHS, but this looked unfamiliar (although it optionally allowed for eliminating the arrows but that looked even more unfamiliar). Keeping in mind Eric S. Raymond’s point about inbound learning curve attrition.
// Note the `tuple` isn’t preceded by a list of PIDs because it would complicate the grammar given we want only one way to write a function declaration with constructed assignment; and because it’s noisy redundany on already very noisy function declarations.
// The PIDs can be deterministically identified in the `type` of the function declaration and ordered alphabetically. PIDs aren’t often explicitly written in an ordered `pid-types` list on function calls anyway.
function-decl:
expression
type [ interfaces ] INDENT block OUTDENT // Lexer issues a CR after last OUTDENT. Thus if misapplied within another expression it’s a syntax error in the containing expression.
interfaces:
'requires' interface { ',' interface }
interface:
TID [ '<' pid-or-associated-type-list '>' ] // TID is a typeclass
pid-or-associated-type-list:
pid-or-associated-type { ',' pid-or-associated-type }
pid-or-associated-type:
PID
interface '.' TID // TID is an associated type of a typeclass
// The grammar is identical for left-to-right and vice versa, and the implicit grouping is interpreted during compilation of the AST.
// Left-to-right precedence operators have only one implicit `)` on the right side of each operator.
// Right-to-left precedence operators have only one implicit `(` on the left side of each operator.
// infix15:
// infix14 { ID infix14 } // Infix variant of function or method application.
infix14:
infix13 { '||' infix13 }
infix13:
infix12 { '&&' infix12 }
infix12:
infix11 { '|' infix11 }
infix11:
infix10 { '^' infix10 }
infix10:
infix9 { '&' infix9 }
infix9:
infix8 { op9 infix8 }
op8:
`==` // Don’t need `===` because no implicit conversions.
`!=`
infix8:
infix7 { op8 infix7 }
op8:
`<`
`<=`
`>`
`>=`
infix7:
infix6 { op7 infix6 }
op7:
'<<'
'>>'
infix6:
infix5 { op6 infix5 }
op6:
'+' // Don’t implicitly convert between types such as strings to numbers or vice versa, e.g. in JavaScript `"string" + 1 + 2 === "string12"`.
'-' // Implicit conversion would require a specific concatenation operator for each type, e.g. `"string" ++ (1 + 2) === "string3"` and `"string" ++ 1 ++ 2 == "string12"`.
// The standard library should have a `To` typeclass: https://github.com/keean/zenscript/issues/39#issuecomment-399445596.
infix5:
prefix4 { op5 prefix4 }
op5:
'*'
'/'
'%'
prefix4:
{ unary4 } instance
unary4:
'!' // Right-to-left precedence.
'-' // Right-to-left precedence.
'&' // Right-to-left precedence; returns a pointer reference.
instance:
literal
postfix3
literal:
INTEGER
FLOAT
STRING
collection
collection:
'[' expression { ',' expression } ']' // Type of the collection is inferred, an explicit type cast, or an explicit type of a constructed assignment.
// The element type might be inferred from the types of the elements.
// An alternative to explicit type annotation is to use the named constructor, e.g. `List(…)`, or for explicit element type, e.g. `List<…>(…)`.
// Any key names are the LHS of assigments in the element expression(s).
// In the absence of any inferred or explicit constraint, unification should default to `HashMap` if keys exist, else `Array`.
postfix3:
infix2 { [ pid-types ] apply } // Optionally apply function or constructor. Assignments in the tuple are named arguments.
pid-types:
'<' { ',' } type { { ',' }+ type } '>' // Optional list of types (allow empty entries) corresponding to the alphabetical order of PIDs in the `type` of the function declaration.
// Necessary for PIDs which can’t be inferred (or when not inferred as intended); the types may contain PIDs from lexical context.
// Also consider optionally allowing Unicode angled brackets ⟨⟩: https://github.com/keean/zenscript/issues/11#issuecomment-412888588
apply:
tuple
':' INDENT { CR expression }+ OUTDENT // Indenting in-lieu of parentheses when the nesting is deep: https://github.com/keean/zenscript/issues/18#issuecomment-402733836
infix2:
container { access }
access:
'[' expression ']' // Keyed or indexed element of a collection.
'.' id-or-tid // TID here is for module and/or typeclass qualifiers. ID is for data type member access not for typeclass members.
container:
id-or-tid
tuple
tuple:
'(' [ expression [ ':' type ] { ',' expression [ ':' type ] } ] ')' // Optional explicit type casts only allowed for arguments of function or procedure application.
Do we support for? Only for iterators, or also in traditional JavaScript form? Do we have a for for enumerables?
Your ideas for syntax?
I think we should retain the ability to construct a var within a for, but I would argue it should have the semantics of ES5's let so the reference doesn't escape the for's scope.
for // infinite loop; employ `break if condition` to terminate loop
for cond
for init; cond; post // any 1 or 2 of the 3 may be omitted except both `init` and `post`
for value in iterable
for key, value in iterable
for key, _ in iterable
The optional init variables (or constant values) only exist in the lexical scope of the for statement.
We will have iterators implemented in libraries instead of Go’s ranges:
foo :: (array) => : [Int] => Int?
last := null // : Int?; Dunno if will need to annotate this type or can be inferred
if !array.isEmpty
iter := iter(array)
last = iter.value
for next(iter)
last = iter.value
last
foo([1, 2, 3])
If instead are willing to sacrifice performance by making either iter or iter.value nullable, or allowing exceptions on illegal access for iter.value, then the code could be written more concisely:
foo :: (array) => : [Int] => Int?
last := null
for iter := iter(array); iter; next(iter)
last = iter.value
last
foo([1, 2, 3])
So I think we should adopt a shorthand syntax sugar which our compiler will expand to the optimally performant encoding for the first example above:
foo :: (array) => : [Int] => Int?
last := null
for value in array
last = value
last
foo([1, 2, 3])
Similar to Go, let’s also offer optional syntax for the key (aka index) for iterables that have it:
foo :: (array) => : [Int] => (Int?, Int?)
last, lastKey := null
for key, value in array
last = value
lastKey = key
(last, lastKey)
foo([1, 2, 3])
Regularity is a more objectivized requirement we can strive for.
Aesthetics and style can be a matter of taste, but there are objective arguments. For example (at least vertical) whitespace is an anti-pattern, because not needing to scroll is more efficient. Another is that symbol (and/or one character names) soup is more difficult to parse. Soup meaning it dominates the context. Some symbols and one letter local variable names can be beneficial. Ditto parentheticals soup. Ditto overly verbose keywords and naming fatigue. Instead balance in context.
So regularity of indent levels is objectively superior. And by the whitespace is an anti-pattern objective, then indent levels should be 2 or 3 spaces.
I wish you would have linked to some of your more specific objective claims about style and other design choices.
I have updated the LL(k) grammar for the proposed syntax of Zer0. It’s not yet completed. Currently it’s still k = 2 which is one token of lookahead and no backtracking. I really don’t want backtracking because I want the fastest possible compiler.
I haven’t yet added the syntax for type system and typeclass. That's next to do.
Note one of the main changes in this update to the work-in-progress is making the function declaration syntax not so unfamiliar, which is also reflected in the edit of the examples in my July 6 post.
Note I have chosen angle brackets for type parameters because:
Contrary to the complaint about parsing ambiguities, I removed the ambiguity by forcing the less than usage to have a whitespace token on both sides and not allowing a space on both sides for all types of brackets. This enables me to reuse the angle left bracket for paired brackets, so as to not waste one of the three bracket pairs (<>, [], {}) available from standard English QWERTY keyboards. I would also like to have optional Unicode support for those who wish to use the real brackets ⟨⟩.
I’d like to use the square brackets for constructing collections and collection keyed (or indexed) access.
I don’t currently have any proposed usage for the curly brackets pair. Does anyone have any ideas? Remember Zer0 is to have a Python-like indenting instead of curly bracketed code blocks.
Does anyone have any opinion or objection about the choice of brackets?
Also I have -> for the impure procedure (but this is pure in Haskell) and => for pure functions (because = is more like a function). Would anyone prefer ~> for impure function? In which case there is also a Unicode equivalent⇝.
P.S. I will probably start a new repository for Zer0 soon and perhaps on Gitlab instead of Github, since Microsoft acquired Github and I have observed Skype becoming more unusable after Microsoft acquired it.
The last syntax is more reasonable. I would prefer m.Monoid, m:Monoid or m=Monoid to indicate associating distinct identifiers with distinct subtypeclass instances of the same supertypeclass.
I was thinking of emulating 'Go' syntax as there does not seem to be a need for the ':' and it just adds noise if it is in every declaration.
Spaces are noise too. The . doesn’t occupy any more screen real estate and it’s more clear as to the semantics. Space is for separating separate things, not for joining related things. That is why I dislike Haskell’s syntax.
With spaces as the join operator then the reader has to consider the context to know what the implicit grouping is. That requires more mental effort and adds another grammar interpretation activity the brain has to do at the same time as interpreting the semantics of the code. It’s also why I hate LISP and Perl. And I also hate those aspects of the Go syntax (including the []Int which is highly irregular). Regularity and ease of grammar and lexical scope processing is very important to me.
Some of us have lower processing of grammar. I am one of those. I even have a mild dyslexia. I can rarely type something correctly the first time. I have high level of processing of concepts and anything that is already loaded into my head, but I am only in the 85 - 88% percentile in verbal I/O. My reading comprehension is high because I process concepts at a high level, but my I/O engine is not at a very high level. So thus I relate to an average programmer better when considering syntax choices. I would not relate well to a below average programmer though.
In terms of the PL conceptual paradigms, I am more apt to agree with choices made by those high IQ math gurus who like Haskell. Which is why for example, I have come to accept many of the paradigms in Haskell. But syntax….
I agree the Haskell syntax is less cluttered. But it’s too spartan and doesn’t read as efficiently for someone with not such high level of grammar processing capabilities. OTOH, I think Ceylon went too far the other direction with too many overly verbose words.
I like camel case for at least for type names, and I think the following does make an argument for employing hyphens instead of underscores at least between uppercase camel humps are separate words:
I think I also prefer camel case but with first letter lowercase for function and procedure names, because camel case employs less characters than hyphens or underscores for delimiting words.
When transpiling to languages which don’t support hyphens in names then need to convert to underscores and check for name collisions if underscores have also been allowed.
However the use of dashes instead of underscores doesn’t conserve any horizontal space with a monospace font and it resembles a crunched infix minus operator. So maybe I should just stick with underscores, if at least for type names?
I guess you'll understand why relying on case sensitivity is a horrible idea only if and when you have to rely on a screen reader to read code out to you -- most screen readers do a horrible job at pinpointing case issues, and there's no really good way, no strong or easy convention to convert case differences to easy auditory clues (while translating underscores to "clicks", in a good configurable screen reader, makes it a breeze). For people without any visual impairments whatsoever, which is no doubt 90% or more, you don't need to care (unless you want to be inclusive and help people who don't share your gift of perfect vision... naah, who cares about those guys, right?!).
And being he was born in Russia, might explain why Yossi Kreinin hates camel case so much:
I've heard it stated in other contexts that words_with_underscores are easier to separate for non-native English readers than are wordByCamelCase. Visually it requires less effort to parse the separate, foreign words.
But I think much of this is perhaps pointless because names shouldn’t be so long and juxtaposed acronyms should be separated with an underscore:
Yet the following example convinces that underscores can be easier-to-read than camelCase in specific cases, even though a study found generally they’re roughly equivalent:
Classes can be kept camel case, giving a clearer difference between them and identifiers/functions. E.g.: CamelRider.ride_camel() vs CamelRider.rideCamel().
[…]
Camel case is easier to type, and underscores are hard to type.
Camel case makes paragraphs easier to read. my_first_variable = my_second_variable-my_third_variable vs myFirstVariable = mySecondVariable - myThirdVariable
Camel case is shorter.
Camel case is used by convention in a lot of major languages and libraries.
This has been true of every text editor I have used.
This is an odd one, but one that definitely, definitely irks me. I can’t Ctrl+Shift+[Arrow key] single words in a camel case string. Take the following screenshot:
Selecting camel case strings
Here I use the Ctrl+Shift+Left arrow keys to select chunks of text–rather than one character–at at time. The problem here is that the camel case string is treated as one word. What if I just wanted to select tweet and change it to facebook? I can’t do that with Ctrl+Shift+[Arrow key]
EDIT#2: here’s the decision I made thus far for the lexer.
Essentially I decided to prioritize hyphens instead of underscores. Yet I still prefer camel case over hyphens where possible, except that identifiers which are not functions nor procedures should be all lowercase and if multiple word, must employ underscores to distinguish them from functions and procedures types.
Thus:
XML-HttpRequest
HTML-HR-Element
* PID token is a type parameter: [A-Z]+
ID token is for values and variables including functions: (?:[αβγδεζηθικλμνξοπρςτυφχψωℎℏ𝑒]|(?:[a-z]|_[a-z])(?:[a-zA-Z0-9]|[a-z0-9](?:_|-)[a-z0-9])*)'*
TID token is for data types, interfaces and constructors: (?:[A-Z]|_[A-Z])(?:[a-zA-Z0-9]|[A-Z][A-Z]-[A-Z])+[a-z0-9]'*
https://github.com/keean/zenscript/issues/11#issuecomment-647137141
Non-exported IDs/TIDs begin with an underscore which is consistent with Pony. An exported ID/non-exported TID can be compiled with the prefix “G”/“g” respectively in Go: https://tour.golang.org/basics/3
Private fields aren’t useful with type class interfaces.
TID only allows hyphens between capital letters, at least one side of which comprises an acronym because our convention is that type names contain only capitalized words. Conversely our convention is that IDs may contain non-capitalized words and/or capitalized words except for first word.
A TID must contain at least one non-capitalized letter to distinguish it from a PID.
The compiler must enforce that an ID for a function or procedure type may contain capitalization and hyphens but not underscores other than the first character; whereas, an ID not for a function or procedure type may not contain capitalization nor hyphens but may contain underscores. This policy discourages use of underscores and making multiple word IDs which don’t have a function or procedure type, while also making a visual distinction between multiple word IDs that have a function or procedure type and those ID which don’t have the said type.
Neither TID nor ID may terminate (a word) with a single uppercase letter nor an underscore nor hyphen.
Activity
keean commentedon Sep 23, 2016
I am not sure I like
pluggablefor a type-class. If it's going to have that many letters eitherinterfaceortypeclasswould be better.I am not sure we want to use
|for both sum types and union types.I prefer having 'implementation' before the type-class having the type class first for implements seems inconsistent to me. I also prefer to treat all type class parameters equally. The first is not special so why give it special syntax.
I am not sure why you put types in the function call syntax? I don't think you need or want them, you only want typed in function definitions.
I don't like that the method syntax is different from the function definition syntax. I think we should have a unified record/struct syntax. If we have:
A record above is like a type-class but you can pass it as a first class value.
If we can 'promote' this to implicit use, we can have a single unified definition syntax. Maybe:
shelby3 commentedon Sep 23, 2016
@keean wrote:
Can't be
interfacebecause it would be confused with the wayinterfaceworks in many other OOP languages. To me as a n00b,typeclassmeansclass, so more misunderstandings.pluggablehas some meaning to a n00b such as myself. Sorry I am not an academic and they are only something like 0.01 - 0.1% of the population.I personally can tolerate
typeclass.Why not? Sum types are an "or" relationship. Unions are an "or" relationship.
Inconsistent with what?
implementation Thing Nilorimplementation Nil Thingare not sentences and it is not clear which one is which.Nil implements Thingis a sentence and very clear which is the typeclass.Afaik, I didn't. What are you referring to?
keean commentedon Sep 23, 2016
Ah I see:
This is ambiguous... is it calling
someCallbackwith 'x' as the first parameter andy => x + yas the second? This would seem less ambiguous:shelby3 commentedon Sep 23, 2016
@keean wrote:
Good catch. I missed that one. It indeed conflicts with comma delimited groups in general, not just function calls. I will remove after sleep.
You didn't point out that problem to me when I suggested it. Remember I was trying to make the inline syntax shorter, to avoid the
_ + __shorthand problems.Edit: there is another option (again
:Typeare optional):someCallback(x:Type y:Type => x + y)But that is still NFG! Because it is LL(inf) because without the leading
(it must backtrack from the space, unless we requireTypeto be a single token in that context (i.e. usetypeif need to define complex type as one token).keean commentedon Sep 23, 2016
Personally I would rather have a single syntax for function definitions. If that is (with
Typeoptional):Then passing to a callback would be:
and then things are consistent. I think keeping things short is important, but I think consistency is even more important.
shelby3 commentedon Sep 23, 2016
@keean the only point was to have an optional shorthand syntax (instead of the inconsistent semantics of
_ + _or the obfuscating_ + __) for inline functions and to get rid of those gaudy juxtaposed parenthesissomeCallback((....Thus we don't need
: Typefor the shorthand syntax, so I propose the following optional shorthand syntax which eliminates the LL(inf) problem as well:someCallback(x y => x + y)Which is shorter than and removes the garish symbol soup
((from:someCallback((x,y) => x + y)That being generally useful shorthand, enables your request for an optional syntax in the special case of single argument (which I was against because it was only for that one special case):
someCallback(x => x + x)Instead of:
someCallback((x) => x + x)However, it isn't that much shorter and the reduction in symbol soup isn't so drastic, so I am wondering if it is violating the guiding principle that I promoted?
Short inline functions might be frequent? If yes, then I vote for having the shorthand alternative since it it would be the only way to write a more concise and less garish inline function in general for a frequent use case. Otherwise I vote against.
keean commentedon Sep 23, 2016
Are we optimising too soon? I have implemented the basic function parser for the standard syntax, is that good enough for now? I think maybe we should try writing some programs before coming back to optimise the notation. I would suggest sticking to "only one way to do things" for now, because that means there is only one parser for function definitions, which will keep the implementation simpler for now. What do you think?
shelby3 commentedon Sep 23, 2016
Thanks for reminding me about when I reminded you about when you reminded others to not do premature optimization.
I agree with not including the shorthand for now. Then we can later decide if we really benefit from it. I'll leave it in the syntax summary with a footnote.
keean commentedon Sep 23, 2016
The compiler can now take a string like this
compile it to:
Next thing to sort is the block indenting, and then it should be able to compile multi-line function definitions and application.
keean commentedon Sep 23, 2016
I think we should have an provisional section, so we can split the syntax into currently implemented, and under consideration.
shelby3 commentedon Sep 23, 2016
@keean wrote:
I'll do if the † instances become numerous enough to justify duplication.
shelby3 commentedon Sep 23, 2016
Link to discussion about unification of structural lexical scope syntax.
keean commentedon Sep 23, 2016
Are we sure having keywords
letandvaris the right way to go? If we have keywords for these we might want to have a keyword for functions? I quite like Rust'sfnfor introducing function definitions?shelby3 commentedon Sep 23, 2016
@keean wrote:
Instead I have proposed unified functions around
letandvar.What would be the alternative to not having
letandvar? I can't think of one that makes any sense. How would you differentiate re-assignment from initialization? Remember we already decided we can't prefix the type for reference initialization, because types are optionally inferred.shelby3 commentedon Sep 24, 2016
@keean wrote:
Agreed.
My suggestions on types of name tokens for the lexer to produce:
dataandpluggable(ortypeclass?):[A-Z]+[a-z][a-zA-Z]*(start uppercase, at least one lowercase, only alphabet)
named function references:
[a-z][a-zA-Z]*non-function and unnamed function references:
[_a-z]+type parameters:
[A-Z]+The exclusivity for type parameters for all uppercase is so they don't have to be declared with
<A,B...>.Edit: the distinction between named functions and non-functions references will be useful, because unnamed functions references should be rarer. However, I was incorrectly thinking that it wouldn't make any sense to give function naming to unnamed function references (which have re-assignable references) because the reference would indicate it is for a function but I had the incorrect thinking the reference could be reassigned a non-function type (but reference types can never change after initial assignment). So I think it would be safe to change the above to:
[a-z][a-zA-Z]*[_a-z]+The other advantage of that is the lexer can tell the parser to expect a function, which is more efficient and provides two-factor error checking.
Note the compiler must check that the inferred type of the reference matches the function versus non-function token for the name.
1165 remaining items
keean commentedon Jun 8, 2017
Personally I don't want one module per file. I have done that in Java, and I didn't like it. You can end up with a directory full of very small files, which makes the code hard to read.. A module is about data hiding and API as well as separate compilation.
I would also not require asynchronous loading, it tends to make applications very slow, and people only tend to use it in development preferring to use tree-shaking tools like rollup to put everything in a single file for production (and minifying it too). I don't even use dynamic loading in development because it makes page load very slow.
shelby3 commentedon Jun 8, 2017
We already discussed your disagreement on this issue. Java’s case was more egregious because as you pointed out that Java requires the filename named according to the class thus forcing a proliferation of files, and I have not proposed such a limitation.
I understand that if modules will be quite small then a proliferation of small files means more files to open instead of just scrolling down. But in theory an IDE can solve this problem and offering to scroll vertically across multiple modules making them appear as though they are in one file. Did no Java IDE ever offer such an improvement? (if not, what has happened to our profession, that programmers are not able to meet the needs of huge ecosystem markets!)
Adding multiple modules within files requires adding a
modulekeyword and a indented-block, and complicates theimportsyntax. Additionally if require that modules are not qualified by file name, then modules can be moved to different files and repositories which afaics will further complicated matters on many levels (including version control coherence).And thus I see no valid reason to have more than one in the same file. A module is a unit of modularity, and thus separating modular items into files makes for clean repository and version control system.
One module per file is compatible with JavaScript and TypeScript. The least amount of unnecessary discord with the target output language is very desirable.
PL design is about extreme prioritization and leaving as much as possible out of the language and not including every little nuanced option we might want to throw in the kitchen sink.
You recently stated that you are coding a lot in TypeScript. How are you coping with this “major downgrade” to one module per file?
Code should be loaded once and then run many times over the course of application, thus any overhead of a
Promiseis irrelevant. If the code is on the local disk, then latency and load time should be rather insignificant. I think the overhead you must be referring to is the HTTP overhead of loading many small files over the wire (or even perhaps local file system latency for accessing many small files)? In that case, the compiler can take care of optimizing by merging small files as you noted (and which I was aware of and already contemplated in my design) and handling this automatically, but the syntax of the programming language need (and should) not be specialized:We do not want specialization in the source code. K.I.S.S. principle (do not eat the complexity budget). I think you are putting design in the wrong abstraction layer:
keean commentedon Jun 8, 2017
Typescript allows multiple namespaces in the same file. It all depends on what you specifically mean by a "module".
Local disc latency is still a problem. You should have a play with dynamic loading tools like SystemJS, obviously it has to load over HTTP, how else can JavaScript load anything (unless you are talking about running in Node.js rather than in the browser, then you can output 'require' statements and have dynamic loading, but it won't work in the browser).
If you compile to TypeScript, then you can use the output options to choose CommonJS, ES6, SystemJS for loading, so it can work in Node or in the browser with dynamic loading or bundled. You would just use the normal TypeScript 'import' statements.
shelby3 commentedon Jun 9, 2017
And afaics it is not really recommended to combine them. It is adding complexity and I do not understand the claimed big win of doing so?
I know sometimes perhaps we need to express privacy access inter-module, so if we supported that feature then we might like to have more than one module in a file (even though it still would not be absolutely necessary). But I did not yet propose offering that feature and probably want to avoid it unless we really need it.
Are you just stating that to document in detail your understanding of what I wrote I would do? Or is there some other reason you mention the details of my solution?
The compiler will optimize it. If I have to fork TypeScript, then so be it. But hopefully I can work within the feature set of TypeScript.
keean commentedon Jun 9, 2017
You wrote you wanted to use promises to dynamically load modules. I stated you were focusing on the wrong abstraction layer and should just output static import statements and let typescript handle it (or implement the same kind of compiler options as typescript if going straight to JavaScript).
Edit: I think I see what you mean, you want to represent module loading as a promise in the source language, not the target language. This is problematic because it means modules must be first-class entities. This means that all module dependencies and relationships need to be expressible in the core type system so that modules a typeable value. This is possible, and is something I have been looking at doing, but it requires careful selection and combining of type system features to make sure the type system is sound.
shelby3 commentedon Jun 9, 2017
@keean wrote (in private):
I was thinking about this also before I read your message. I then thought for those who want these groupings of modules, we could offer a grouping keyword and they could place this in a separate file. A smart editor would know when loading the said file, to also load the referenced modules as a linear text view. With this separation-of-concerns, we K.I.S.S. principle the base format for modules keeping them coherent with EMCAScript modules (one per file) and prevent clutter of
modulekeyword and block-indents in module files. Yet we can also then orthogonally meet the need of those who want a static grouped view of related modules. And this interops better with version control in that the grouping changsets are orthogonal to the module changesets.Win-win design via separation-of-concerns.
I understand it looses the simplicity of being able to see the linear text grouping with any unspecialized text editor (e.g. Notepad). But it does not loose the power of reasoning about language orthogonal to GUIs. And if force everything to be literally copied in text, this is less powerful than structure by named reference, e.g. we would prefer
importby named reference and not by copying the entire text of an external module into the top of every module that references a module.shelby3 commentedon Jun 10, 2017
I understand maybe I did not sufficiently explain my proposal.
Copying TypeScript’s method where all imported typings from the module are resolved statically and if none of the runtime code of the module is accessed then nothing is loaded at runtime. Any runtime code (and state) is loaded via an asynchronous model before it can be accessed (either via an explicit assignment of the
importto a reference in our language's source code or otherwise an implicit such reference which appears in the transpiled output code). And runtime loading must be optimized (e.g. for some target environments, the compiler might even have everything statically loaded or load modules in large amalgamated chunks).I do not understand why you think this introduces type soundness issues? TypeScript does not seem to think so? Perhaps you are thinking that the types in the module would first-class? I did not intend to propose that. I am proposing that only the object of (non-
private) runtime objects (e.g. the constructor functions and other functions and any state) is first-class; and its type is determined by the items imported. The types are statically resolved. Does that clear up the issue? Perhaps you have a more complex model of module dependencies in mind? Essentially my proposal is just runtime linking and afaics should not have any impact on the type system.keean commentedon Jun 10, 2017
A module is a unit of data hiding like an object. It doesn't necessarily have anything to do with files. See 'ML modules'.
shelby3 commentedon Jun 10, 2017
Also type hiding.
Why have files? Let’s just put the entire program in one file. Or better yet, let’s put all programs that were ever created and will ever be created in one file. Obviously modularity has nothing to do with files.
I think the more salient question is not whether we need more than one module per file, rather whether we need more than one file per module? I am approaching modules conceptually as units of reusable repositories in an open source ecosystem similar to Rust Crates or npmjs.
ML must be one of the most popular languages. Why are we creating a language. There is already an OCaML to JavaScript transpiler. I believe we discussed that typeclasses can be simulated with OCaML’s features. One of the reasons I provided was because we would lose integration with JavaScript such as
Promises and the concepts and syntax are too foreign. I basically wanted to home in on a very elegant K.I.S.S. design that would not be too obtuse for a mainstream, popular language. 80/20 prioritization.So instead of appealing to authority of ML, could you instead provide examples of which features where our modules will require multiple modules per file or multiple files per module? Because I am not really healthy enough to expend the effort to research ML modules and try to figure out myself which uses cases dictate such.
Edit: modularity-of-encapsulation might not be equivalent to modularity-of-reuse. A module-of-reuse might need more than one module-of-encapsulation, i.e. that each of modules of encapsulation is incomplete without each other. Yet this is expressed by typing and the consumer of the module-of-reuse has to
importall the required modules of encapsulation. Yet maybe we wish to be able toimportall as a grouping. This is easy to do. A module canimportother modules, which satisfies my prior suggestion for a way to group modules. As I previously suggested, a smart editor can offer an option to display multiple grouped modules in a single vertical scrolling amalgam.The reason to put each module-of-encapsulation in a separate file is so that it can be referenced orthogonally by another module-of-reuse, which reuses only some of the modules of encapsulation. K.I.S.S.. rather than add complexity. If a lot of small files makes it difficult to see the modules of reuse that group these small modules of encapsulation, then put them in a separate directory (folder) in the file system.
shelby3 commentedon Jun 24, 2018
This is LL(k=2) thus far. Only 1 token of lookahead. I will update this as I progress to add the rest of the grammar. Eventually this will be added to a new Zer0 repository.
shelby3 commentedon Jul 5, 2018
I wrote in 2016:
I propose for Zer0 to adopt Go’s decision to only support
forloops (c.f. also how to simulatewhileanddo…while), and to expand the syntax options:The optional
initvariables (or constant values) only exist in the lexical scope of theforstatement.We will have iterators implemented in libraries instead of Go’s ranges:
If instead are willing to sacrifice performance by making either
iteroriter.valuenullable, or allowing exceptions on illegal access foriter.value, then the code could be written more concisely:So I think we should adopt a shorthand syntax sugar which our compiler will expand to the optimally performant encoding for the first example above:
Similar to Go, let’s also offer optional syntax for the key (aka index) for iterables that have it:
And when only the key is desired:
See the example which exemplifies how much more readable this is as compared to the traditional
forin C, C++, JavaScript, and Java.shelby3 commentedon Aug 4, 2018
I wrote on Quora:
I wrote on Quora:
shelby3 commentedon Aug 14, 2018
I have updated the LL(k) grammar for the proposed syntax of Zer0. It’s not yet completed. Currently it’s still
k = 2which is one token of lookahead and no backtracking. I really don’t want backtracking because I want the fastest possible compiler.I haven’t yet added the syntax for type system and typeclass. That's next to do.
Note one of the main changes in this update to the work-in-progress is making the function declaration syntax not so unfamiliar, which is also reflected in the edit of the examples in my July 6 post.
Note I have chosen angle brackets for type parameters because:
Contrary to the complaint about parsing ambiguities, I removed the ambiguity by forcing the less than usage to have a whitespace token on both sides and not allowing a space on both sides for all types of brackets. This enables me to reuse the angle left bracket for paired brackets, so as to not waste one of the three bracket pairs (
<>,[],{}) available from standard English QWERTY keyboards. I would also like to have optional Unicode support for those who wish to use the real brackets⟨⟩.I’d like to use the square brackets for constructing collections and collection keyed (or indexed) access.
I don’t currently have any proposed usage for the curly brackets pair. Does anyone have any ideas? Remember Zer0 is to have a Python-like indenting instead of curly bracketed code blocks.
Does anyone have any opinion or objection about the choice of brackets?
Also I have
->for the impure procedure (but this is pure in Haskell) and=>for pure functions (because=is more like a function). Would anyone prefer~>for impure function? In which case there is also a Unicode equivalent⇝.P.S. I will probably start a new repository for Zer0 soon and perhaps on Gitlab instead of Github, since Microsoft acquired Github and I have observed Skype becoming more unusable after Microsoft acquired it.
shelby3 commentedon Aug 17, 2018
I wrote in the Subtyping thread:
shelby3 commentedon Jun 21, 2020
I like camel case for at least for type names, and I think the following does make an argument for employing hyphens instead of underscores at least between uppercase camel humps are separate words:
https://yosefk.com/blog/ihatecamelcase.html
https://wiki.theory.org/index.php/YourLanguageSucks#Poor_Design
I think I also prefer camel case but with first letter lowercase for function and procedure names, because camel case employs less characters than hyphens or underscores for delimiting words.
When transpiling to languages which don’t support hyphens in names then need to convert to underscores and check for name collisions if underscores have also been allowed.
However the use of dashes instead of underscores doesn’t conserve any horizontal space with a monospace font and it resembles a crunched infix minus operator. So maybe I should just stick with underscores, if at least for type names?
P.S. The author of the aforelinked blog is also the author of the C++ Frequently Questioned Answers satire.
EDIT: another point about verbalization of case:
https://stackoverflow.com/questions/1740116/for-what-reason-do-we-have-the-lower-case-with-underscores-naming-convention/1740152#1740152
And being he was born in Russia, might explain why Yossi Kreinin hates camel case so much:
https://stackoverflow.com/questions/1740116/for-what-reason-do-we-have-the-lower-case-with-underscores-naming-convention/1740131#1740131
But I think much of this is perhaps pointless because names shouldn’t be so long and juxtaposed acronyms should be separated with an underscore:
https://stackoverflow.com/questions/1740116/for-what-reason-do-we-have-the-lower-case-with-underscores-naming-convention/1740364#1740364
https://stackoverflow.com/questions/1740116/for-what-reason-do-we-have-the-lower-case-with-underscores-naming-convention/1744749#1744749
Yet the following example convinces that underscores can be easier-to-read than camelCase in specific cases, even though a study found generally they’re roughly equivalent:
https://softwareengineering.stackexchange.com/questions/27264/naming-conventions-camelcase-versus-underscore-case-what-are-your-thoughts-ab/27293#27293
Camel case has much less symbol soupy noise:
https://whatheco.de/2011/02/10/camelcase-vs-underscores-scientific-showdown/
In a block indenting, no curly brace style:
[…]
[…]
Editor considerations:
https://csswizardry.com/2010/12/css-camel-case-seriously-sucks/
EDIT#2: here’s the decision I made thus far for the lexer.
Essentially I decided to prioritize hyphens instead of underscores. Yet I still prefer camel case over hyphens where possible, except that identifiers which are not functions nor procedures should be all lowercase and if multiple word, must employ underscores to distinguish them from functions and procedures types.
Thus: