Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.


Messages - lerno

Pages: 1 ... 6 7 [8] 9 10 ... 17
106
General Discussion / Re: Overview of syntax
« on: November 10, 2018, 06:06:20 PM »
I've worked quite a bit in Ruby. If found the lack of ";" makes single line conditionals and loops less nice to work with.

107
Ideas / Re: Naming restrictions
« on: November 10, 2018, 04:01:57 PM »
Interestingly, if we enforce the type casing we can simplify analysis quite a bit:

  • Types are the built in ones (as keywords) + anything starting with uppercase.
  • Identifiers are anything starting with lowercase (i.e. variables, function names)
  • Initial underscores are allowed and skipped when determining against these rules (so __Foo is a type, __foo is a
    an identifier)
  • Reserve anything starting with a single _ for user use (so allowed), double __ is reserved for internal use, so is disallowed. Same rules inside of indentifiers/types: Foo_bar is allowed, Foo__bar is not allowed.
  • Reserve __x_, __X_ prefix to bypass name rules externally. So __x_Foo would match an external symbol "Foo" assumed to be a function/variable. __X_foo would match an external symbol "foo" assumed to be a type. An exported function __x_Foo would be exported externally as Foo to C/C++.


108
Ideas / Re: Why "recipe.txt" ? :)
« on: November 10, 2018, 03:48:47 PM »
Jai has a #bake directive and is probably the more optimized language for ovens.  :o

109
Ideas / Re: Keyword "func" - redundancy?
« on: November 10, 2018, 03:47:41 PM »
I've written about it before, but my opinion is that type inference of variables is mostly driven by generics.

If you have

Code: [Select]
std::vector<std::shared_ptr<Foo>> x = foo();
return x.size() > 0 ? x[0] : std::shared_ptr<Foo>();

Then obviously

Code: [Select]
auto x = foo();
return x.size() > 0 ? x[0] : std::shared_ptr<Foo>();

Is a great relief, but if the code was:

Code: [Select]
Foo *x[] = foo();
return x.size() > 0 ? x[0] : NULL;

Then type inference makes the code worse:

Code: [Select]
auto x = foo();
return x.size() > 0 ? x[0] : NULL;

110
Ideas / Re: Add defer
« on: November 09, 2018, 09:46:27 PM »
Go essentially creates a "defer list" with closures it will invoke when the function ends. It's pretty weird.

Golang's defer { close(foo) } could in C++ be implemented like this:

Code: [Select]
int do_something()
{
   defer_list __defer_list {}; // Always at top
   ... code ...
   __defer_list.push_back([foo] { close(foo); }); // defer { close(foo) } in Go
   ... code ...
   return 0;
}

defer_list::push_back(std::function<void()> &closure) { entries.push_back(closure); }

~defer_list() {
  for (auto &defer_entry : entries) {
    defer_entry();
  }
}

So if you do something like this:
Code: [Select]
func main() {
  for i:= 0; i < 3; i++ {
    defer fmt.Println(i)
  }
  fmt.Println("foo")
}

You'd get:

Code: [Select]
foo
2
1
0

So there's an actual defer stack in Go! So weird.

Rust, Zig, Jai all use the more reasonable RAII-like defer.

111
Ideas / Re: Does C2 support this? Otherwise it should. "Labels as values"
« on: November 09, 2018, 09:32:18 PM »
I can be used to create extremely fast "jump tables" for switches. Actually, identifying keywords is a very good usecase for it. Of course, many compilers already implement switches as jump tables. This is a way to do what otherwise would need a function call.

Consider:

Code: [Select]
int foo(int x) { return x + x; }
int bar(int x) { return x * x; }
int baz(int x) { return x * x + x; }

static inline int dispatch_on_type(enum Foobar foobar, int x) {
  static int (*foobars[3])(int) = { foo, bar, baz };
  return foobars[foobar](x);
}

This code requires loads and function calls. Compare this to using labels:

Code: [Select]
int foo(int x) { ... }
int bar(int x) { ... }
int baz(int x) { ... }

static inline int dispatch_on_type(enum Foobar foobar, int x) {
  static void **foobars[3] = { &&foo, &&bar, &&baz };
  goto *foobars[foobar];
  foo:
  return x + x;
  bar:
  return x * x;
  baz:
  return x * x + x;
}

It's easy to recognize this as what the compiler already does when optimizing a switch. The point is that this adds additional flexibility that isn't possible in a plain switch. I like it but not priority 1.

112
Ideas / Re: Anonymous structs (actually, tuples)
« on: November 08, 2018, 07:21:34 PM »
Well, it enables "multiple return types", but it's actually just partial assignment from a struct.

Let's say you have this:

Code: [Select]
type Foo struct {
  i32 a;
  f64 b;
}

func Foo getFoo() { ... }

Now obviously it's possible to do:

Code: [Select]
Foo f = getFoo();
i32 a = f.a;
f64 b = f.b;

What we allow is destructuring, that is:
Code: [Select]
i32 a; f64 b; Foo f;
{ a, b } = f; // or (a, b) = f,  struct { a, b } = f, or whatever syntax we pick.

What I like about this approach is that there's no magic. There is no tuple type, just a plain struct. It feels magic, but all we do is to assemble our variables into an ad-hoc struct to receive the reply.

It's merely a quick way of assigning fields from structs that mirror how we can create structs.

I mean we can do this:

Code: [Select]
f = { .a = a, .b = b };
f = { a, b };

And what we enable is the reverse, so basically

Code: [Select]
{ a, b } = f;
{ a = .b, b = .b } = f; // <- placeholder syntax

The reason why we want the latter (the one with placeholder syntax) is if we have a very large struct, say with 5 fields, and we like to keep 3 of them. Also, it might be useful if we don't remember the order of the fields.

So the feature is struct destructuring, and what we get is multiple returns.

113
Ideas / Re: Switch statement
« on: November 08, 2018, 05:06:52 PM »
What did you think of

case 1 | case 2: foo(); above?

To clarify:

Code: [Select]
switch (a) {
  case 1 |  // Using : instead of | is syntax sugar for case 1: fallthrough;
  case 2 |
  case 3:
    foo();  // With : we get a break;
  case 4 |
  case 5:
    bar();
    fallthrough; // If we want to execute a statement then pass to next.
  case 6:
    baz();
}

// The above in C:
switch (a) {
  case 1:
  case 2:
  case 3:
    foo();
    break;
  case 4:
  case 5:
    bar();
  case 6:
    baz();
    break;
}

Another possibility is |: or :|

Code: [Select]
switch (foo) {
  case 1|:
  case 2|:
  case 3:
     do_something();

Using | means a that some care must be taken if there is an expression of type:

case 1|2: (This is is compiled to case 3: or course, but I think it's reasonable to require ( ) on any constant expression that isn't a literal.

So case 1+2: would not be allowed, but case (1+2): is ok.

114
Ideas / Re: One more big thing: Errors
« on: November 08, 2018, 03:59:04 PM »
The error handler form has advantages and disadvantages. Common LISP has error handlers, and that was also how the primitive error handling worked in more advanced versions of BASIC "ON ERROR GOTO..."

Error handlers are a lot like try/catch, but with less nesting issues. The explicit errors code returns are AWESOME for determining local code flow but propagating / handling errors creates a lot of checks.

But there are two orthogonal things really:

1. How does the syntax look
2. How is it implemented

Let's focus on the implementation first, then discuss syntax:

  • Exceptions - this means in practice that any call might cause unwinding of the stack.
  • Ad hoc (error codes, sending in a variable to hold the error etc)
  • tagged union (struct { bool, union { Result; ErrorCode } })
  • Like tagged union but with bool returned in register/flag
  • return thread local Error in Context (also used with allocator)

I don't like (1) (2). I think the memory overhead of (3) can be an issue.

So I like 4 or 5.

- 4 can use a fallback (and initial implementation) like (3)
- I don't know how straightforward (4) is to implement in LLVM
- 5 costs a memory access, so 4 is cheaper.
- 5 is straightforward to implement in LLVM

115
General Discussion / Re: Overview of syntax
« on: November 08, 2018, 03:38:58 PM »
I'm actually a fan of ";". It makes macros and generated code so much easier to do.

116
General Discussion / "Code of least surprise"
« on: November 08, 2018, 03:37:54 PM »
I think the one of the best features of C (which it shares with languages like Pascal, and in some sense Java) is that it's very clear from the code what is happening. The only thing that breaks the rule is macros.

Contrast to that to a language that is the complete opposite: Swift. In Swift, you can express opaque DSLs trivially. Overloading on return type is pretty crazy, as is the implicit conversions of C++.

I would call C a language with "code of least surprise" – what you see is what you get. If you analyse a few lines of code you can actually directly know how the execution will flow, what types variables are etc, instead of things happening implicitly "behind the scenes".

My understanding is that C2 is pretty much the same: as far as it possible no unseen, implicit code that works behind the scenes creating surprising effects (Swift, C++). If so, maybe state explicitly it in "philosophy"?

117
Ideas / Re: Relax variable initialization rules
« on: November 08, 2018, 03:13:06 PM »
I don't necessarily think that my proposed code is *needed*, instead I think it simplifies the rules of the language, which makes sense to me. Any rule "you can do this in some cases but not others" *will* be a bit arbitrary. Since this should not alter complexity of the language (when the analyser finds the code, it should edit the AST to reduce it to the above code for both C and LLVM)

To illustrate the complexity issue. Imagine this as refactoring steps:

Code: [Select]
const i32 foo_one = 31;
func void foo()
{
   i32[foo_one + 1] array = {
     [foo_one] = 1;
   }
   ...
}

"We let's make it more generic!"

Code: [Select]
func void foo(i32 one)
{
   i32[one + 1] array = {
     [one] = 1;
   }
   ...
}

"Let's clean it up"

Code: [Select]
func void foo(i32 one)
{
   i32[one + 1] array;
   array[one] = 1;
   ...
}

So more of an enabler of small steps of rewriting. In this case it's trivial, but consider if it's embedded in a bigger context. So it's not that I think it's a good idea for the end code, but code of "least surprise".

118
Ideas / Re: Dynamic arrays & fixed arrays
« on: November 08, 2018, 01:32:07 PM »
I think there is a very real advantage in making it a first class object. My proposal is obviously not well fleshed out yet, but it's worth looking at Cyclone and how it added a "fat pointer", which basically is the pointer + the size of how much was allocated in it. That's a very generic way of handling everything that might make sense (after giving it some remodelling)

In Cyclone another pointer type is added: "@". So Foo@ would be a fat pointer to Foo (or an array of Foo), and Foo* would be the raw pointer.

I think there is something to consider here. Still, as I say I do not have a full proposal, just noticing that it would improve a lot of the ergonomics if there was more built in support to handle arrays and pointers. Not just for security reasons.

119
Ideas / Re: Enum improvements
« on: November 08, 2018, 01:27:52 PM »
.min / .max raises a larger question:

Should there be struct "constants"?

So
Code: [Select]
type Foo struct {
  ...
}

const Foo.pi = 3.0;

...

func f64 Foo.pie_c(f64 r) { return Foo.pi * r * 2; }

If so, then enums simply emit const TheEnum.min = ...; const TheEnum.max = ...;

Regarding the enum values... it's extremely common to use X macros to actually do that anyway - to generate the switch automatically with whatever you want. For example you might define:

Code: [Select]
#define types \
  X(i8, 8, true) \
  X(u8, 8, false) \
  X(i16, 16, true) \
  X(u16, 16, false) \
/* ... etc */


bool isSigned(type t) {
  switch (t) {
#define X(a, b, c) case a: return c;
    types;
#undef X
  }
   

This occurs a lot when writing protocols or parsers, but also when describing simple state machines. Anytime you have enums really.

Associated values is the simplest way I know to express this (and it's a fairly small addition that has extremely powerful effects, consider

Code: [Select]
type enum State [char*(int) action] i32  {
   START(startFunction) = 0,
   END(endFunction)
}

state.action(10); // Calls startFunction(10)

It's not really important HOW it is implemented, just that it is there. The downside of the X macro solution in C is that it's very messy for IDEs to parse and it's also hard to read the code to know what happens (just look at Clang!)

120
Implementation Details / Re: Parsing numbers
« on: November 07, 2018, 04:58:45 PM »
Yes most of this is already in the Clang lexer, so we get them "for free". But it should be documented.

Pages: 1 ... 6 7 [8] 9 10 ... 17