Author Topic: Antelope (formerly "OPIA") - A Polymorphic z80 language  (Read 40918 times)

0 Members and 1 Guest are viewing this topic.

Offline shkaboinka

  • LV3 Member (Next: 100)
  • ***
  • Posts: 86
  • Rating: +10/-1
    • View Profile
    • Antelope (polymorphic z80 language)
Re: Antelope (formerly "OPIA") - A Polymorphic z80 language
« Reply #135 on: July 19, 2012, 01:13:09 am »
Because I took a couple pages to say it in detail (and because I've been told not to link to other forums), I will just sum it up:

With the switch from a C# style to a Go style, I figured that interfaces provide enough dynamic behavior to be alright without providing parametric types ("templates" in C++, "generics" in Java), since you can just use an interface to store data of any type. The empty interface ("interface any{ }") acts as a pointer to anything without any method requirements (like "void*" in C++ or "Object" in Java).

The problem is that using a more complete interface means that each entry is larger, since the relevant function pointers are stored alongside them. For example, each item in "data" below contains a pointer to some object, AND pointers to all the methods of the interface:

Code: [Select]
interface Printer { Print(); }
struct Foo { []Printer data; }

One solution would be to use Parametric types as a convention for storing the method-pointers separately. It would look something like this, with "<T:Printer>" designating that the struct separately stores ONE copy of method-pointers for the "Printer" interface, and designating "T" as a stand-in for something that counts as a "Printer" (i.e. something with a Print method, but not an actual "Printer" itself):

Code: [Select]
interface Printer { Print(); }
struct Foo<T:Printer> { []T data; }

This would require each "T" to be stored as a pointer (rather than a whole "Printer") so that the same struct (or any parameterized entity)can be used for anything.  Otherwise there would have to be a different "version" for each type, and it would be pointless to actually store implicit method pointers for the interface, since each version could have the "right one" hard-coded in. In either case though, this struct would be used with a datatype plugged into it, which the compiler would use to restrict what can be stored in it:

Code: [Select]
Foo<int> list; // list may only work with ints (and stores an int.Print pointer)
Foo<Bar> list2; // list2 only works with Bars (stores a single Bar.Print pointer)

The issue is two things (and a couple of trivial things that I will leave out):

The first is that it does not parse well ("x<y,z>w" could be a tuple of two boolean comparisons, it it could be saying that "w" is somthing of type "x<y,z>"). This can be solved by using an unconventional syntax, or by requiring the "tuple version" to use extra parenthesis to work properly, or by reparameterizing each parametric type (e.g. "x<y><z>").

The second is just whether to use the "generic" form (all parametric types are pointers) or the "template" form (the compiler makes a copy of everything for EACH type used). The first only uses one version of everything and provides true dynamic entities. The second makes multiple copies which are hard-coded per type, but each is more efficient; however, the copies depend on what is used per compile, and there cannot be one precompiled version (set of methods), so it could not exist as a runtime library unless the "generics" setup is used.
« Last Edit: July 19, 2012, 01:15:51 am by shkaboinka »

Offline BlakPilar

  • LV8 Addict (Next: 1000)
  • ********
  • Posts: 734
  • Rating: +44/-1
    • View Profile
Re: Antelope (formerly "OPIA") - A Polymorphic z80 language
« Reply #136 on: July 19, 2012, 04:04:35 pm »
If it's my understanding, then this:
Code: [Select]
interface Printer { Print(); }
struct Foo<T:Printer> { []T data; }
could just as easily be this:
Code: [Select]
interface Printer { Print(); }
struct Foo<Printer> { []Printer data; }
because inheriting from Printer would automatically make it a Printer type. Having something like struct Foo<T:Printer> ... is far more complicated than necessary because if something has a Print() method, but is not yet a Printer, it would be just as easy to simply make it inherit from Printer.

As for the parsing bit ("x<y,z>w"), maybe you could adopt a sort of Lisp syntax for that and drop the comma ("x<y z>w")?

Also, interfaces are kind of templates by definition, at least in C#. Not every Print() method would be the same for every Printer. They would each contain a definition for the method, but the bodies may be different, so I would say go with the template form. (That is, unless I'm understanding this wrong.)

Offline shkaboinka

  • LV3 Member (Next: 100)
  • ***
  • Posts: 86
  • Rating: +10/-1
    • View Profile
    • Antelope (polymorphic z80 language)
Re: Antelope (formerly "OPIA") - A Polymorphic z80 language
« Reply #137 on: July 20, 2012, 04:56:54 pm »
If it's my understanding, then "struct Foo<T:Printer> { []T data; }" and "struct Foo<Printer> { []Printer data; }" are basically the same. ... Not every Print() method would be the same for every Printer.

Sort of... The intention is that, where the Print() method is the same, you would only have to store one of them. For example, a Foo<Bar> would be a "Foo" containing "Bar" values, and therefor each using the same "Bar.Print" method. If you want a structure of mixed "Printers", then you would just use the Interface directly:

Code: [Select]
interface Printer { Print(); }

// Using "mixed" Printers ("<>" is unnecessary)
struct Mixed { []Printer data; }
Mixed m = {{someBar, someCar, someDar}};
//m is: {{Printer{someBar,Bar.Print}, Printer{someCar,Car.Print}, Printer{someDar,Dar.Print}}}
m.data[i].Print(); // Calls the "Print" of data[i] on the Printer-object of data[i]

// Using only same-typed Printers:
struct Same<T:Printer> { []T data; }
Same<Bar> s = {{bar1, bar2, bar3}};
// s is: {{bar1,bar2,bar3}, Bar.Print}
s.data[i].Print(); // Calls the single embedded Bar.Print on data[i]

The idea with this style is that the parameterization (<T:Printer>) just acts as a way to base a structure on some type which qualifies as some interface, but to store it as is and use the relevant method. The method must still be stored (since the structure does not know which type it ever since, being a generic structure based on a pointer to "something"), but is stored separately for efficiency. However, if you want a bunch of Printers (not things based on Printers, but actual Printers), then just use the Printer type directly!

The conundrum again is as follows:

This "generic" style has the "win" of providing a more efficient "generic" version. You could even have a plain "Foo" as a function argument, and pass it either a "Foo<Bar>" or a "Foo<int>", and it will work all the same using the same function, because the relevant method is embedded. Besides, a "templated" form is a convenience which CAN be coded by hand. That is, if you want a structure of int values, then go ahead and make one.

HOWEVER, without a "template"-based setup, it is impossible to make a standard library for such. For example, someone could make a very nice tree structure based on a "generic" type, or even just on an int; but if you wanted one made JUST for strings, you would have to code it all over again just for strings. So a "templated" setup has a win there; but would then not allow for the generic behavior I just described, result in multiple vesions of everything (bloat), and it would be impossible to have a runtime (precompiled) library of such, since every possible type cannot be anticipated that way.

C# actually uses something between the two forms (it uses one definition of everything, but the "reifies" them at runtime ... something like that) ... I am going to go read more on how C# does it and see if I get any ideas; though Antelope does not use a virtual environment and JIT is not applicable.

By the way, that "<A B>" syntax might not be a bad idea; though it would be the only thing in the language which does not use a separator (comma or semicolon) between arguments...
« Last Edit: July 20, 2012, 05:06:22 pm by shkaboinka »

Offline BlakPilar

  • LV8 Addict (Next: 1000)
  • ********
  • Posts: 734
  • Rating: +44/-1
    • View Profile
Re: Antelope (formerly "OPIA") - A Polymorphic z80 language
« Reply #138 on: July 20, 2012, 11:16:01 pm »
Oh okay. I see what you mean. But then again, instead of "<T:Printer>," you could just make Printer an abdtract class with a default method and only make a new instance of the code body if it overridden in a class that inherits from Printer.

Also, are you sure Foo and Foo<T> would have the same methods? The former would be of type Foo, but the latter's type would probably be something like IEnumerable<T>.

Offline shkaboinka

  • LV3 Member (Next: 100)
  • ***
  • Posts: 86
  • Rating: +10/-1
    • View Profile
    • Antelope (polymorphic z80 language)
Re: Antelope (formerly "OPIA") - A Polymorphic z80 language
« Reply #139 on: July 22, 2012, 06:18:48 pm »
1) you could just make Printer an abstract class with a default method
2) Also, are you sure Foo and Foo<T> would have the same methods?
3) Foo<T>'s type would probably be something like IEnumerable<T>.

1) There are no classes in Antelope, and no "abstract" mechanism. There are just structs, interfaces, and methods, which are all separate features (to be very technical, nothing "has" methods, but there may "be" methods "for" something; they are given additionally). However, you can use embedded function-pointers to simulate some of that behavior:

Code: [Select]
struct abstract {
   int x, y;
   meth := func(this, int a, int b) { x=a; y=b; };
}

a1 = abstract{1, 2}; // uses defualt method
a2 = abstract{1, 2, someOtherMethod};

2) I am actually leaning toward the "templating" setup, so that if there is a "Foo<T>", then it will be incorrect to have a plain "Foo". The compiler will generate different methods for each type used, but will share methods which would be exactly the same. This means that you can use a pointer to keep everything generic:

Code: [Select]
struct Foo<T> { *T value; } // always the same size
func Foo<T>.Bar<T>() { ... } // always the same everything
func Foo<int>.Bar() { ... } // THIS one is specific to ints.

c := Foo<char>{'5'};
b := Foo<bool>{true};
i := Foo<int>{123};

c.Bar(); // inferred as c.Bar<char>, or Foo<T>.Bar<T> where T = char
b.Bar(); // inferred as b.Bar<bool>, or Foo<T>.Bar<T> where T = bool
i.Bar(); // inferred as i.Bar<int>, or the separate Foo<int>.Bar

// --- Another just to clarify: ---
func A<T>(T arg) { ... } // Different per type-size (and per differing operations on T)
func B<T>(*T arg) { ... } // Same per type, so long as nothing type-specific happens

3) ...Nothing would be quite like "IEnumerable<T>"; though yes, they share the same kind of generic idea. On a side note though, Antelope abandons the concept of "iterators" as they are in Java, C++. Instead, you'd iterate over things directly within a cofunc (which, by the way, was inspired by the "enumerators" or C# and the coroutines of Lua):

Code: [Select]
struct Tree<T> { T value; *Tree<T> left, right; }

cofunc Tree<T>.InOrder<T>():T {
   if(left != null)
      for(int i : new left.InOrder{}) { yield i; }
   yield value;
   if(right != null)
      for(int i : new right.InOrder{}) { yield i; }
}

Tree<int> tree; // Tree of int values
...
for(int i : tree.InOrder{}) { Print(i); }

EDIT: I actually found that using function-pointers as "visitors" is a MUCH more efficient pattern:

Code: [Select]
struct Tree<T> { T value; *Tree<T> left, right; }

func Tree<T>.InOrder<T>(func(T) process) {
   if(left != null) { left.InOrder(process); }
   process(value);
   if(right != null) { right.InOrder(process); }
}

Tree<int> tree;
...
tree.InOrder(Print);
// Or: tree.InOrder(i => somethingElse(i));
(SEE MY NEXT POST)
« Last Edit: July 24, 2012, 05:16:28 pm by shkaboinka »

Offline BlakPilar

  • LV8 Addict (Next: 1000)
  • ********
  • Posts: 734
  • Rating: +44/-1
    • View Profile
Re: Antelope (formerly "OPIA") - A Polymorphic z80 language
« Reply #140 on: July 22, 2012, 08:30:25 pm »
Ahh okay, I think I understand this all now.

Offline shkaboinka

  • LV3 Member (Next: 100)
  • ***
  • Posts: 86
  • Rating: +10/-1
    • View Profile
    • Antelope (polymorphic z80 language)
Re: Antelope (formerly "OPIA") - A Polymorphic z80 language
« Reply #141 on: July 23, 2012, 04:56:53 pm »
*Bump* - Here are some new changes:

I have reverted the "cofunc" syntax back so that they cannot be embedded within structs, but they can hold data like structs. This is slightly less flexible (only because bizarre"siamese" cofuncs are no longer possible), but is more intuitive:

Code: [Select]
// Old setup:
struct Counter { int n; cofunc( ):int { n++; return n; } }
c := Counter{5};
c(); c(); c(); // 6, 7, 8

// New setup:
cofunc Counter { int n; } ( ):int { n++; return n; }
c := Counter{5};
c(); c(); c(); // 6, 7, 8

I have also decided to go with the "Templating" form of parametric types (though I still need to choose an unambiguous syntax for multiple parameters), but I will have templated things share the same functions when they are the same. This means that you can still use POINTERS to keep things generic for all types :)

Code: [Select]
struct Link<T> { T value; *Link<T> next = null; }
struct List<T> { *Link<T> head = null, tail = null; length = 0; }

func Tree<T>.Size<T>():int { return length; }

func Tree<T>.add<T>(T val) {
   l := new Link<T>{val};
   if(head == null) { head = tail = l; }
   else { tail = tail.next = l; }
   length++;
}

List<int> list; // an int list
list.add(1); ist.add(1); ist.add(3);
If it were "*T value" rather than "T value" in Link, then there'd only one version of everything, since pointers are the same size. Also, you can be specific, such that a "func List<int>.Foo()" would take ONLY int-lists (which is why the extra "<T>" is needed in "List<T>.add<T>").

I have also added the option of an extra "this" for cofuncs, removed the empty parenthesis from no-argument lambdas, allowed lambdas to have full {bodies}, ... and in the process, I have discovered that passing a function-pointer is MUCH more efficient than using a cofunc as an "iterator". Here is an example of all (riding off that "List" code):

Code: [Select]
List<int> list;

cofunc List<T>.Iterator<T>():*T {
   for(l := head; l != null; l = l.next) yield l.value;
}

func List<T>.Each<T>(func(*T) process) {
   for(l := head; l != null; l = l.next) process(l.value);
}

// Cofunc iteration (uses two loops!)
for(int i: list.Iterator{}) { ...do stuff with i... }
// Func-pointer iteration (much more efficient)
list.Each(i => {...do stuff with i...});

The only thing is that you cannot encapsulate everything into a function; though perhaps I can allow direct access to local variables, but then disallow such funcs from "escaping" the embedded function by restricting their use (e.g. cannot be assigned to external variables or passed into external routines).
« Last Edit: July 24, 2012, 05:19:25 pm by shkaboinka »

Offline shkaboinka

  • LV3 Member (Next: 100)
  • ***
  • Posts: 86
  • Rating: +10/-1
    • View Profile
    • Antelope (polymorphic z80 language)
Re: Antelope (formerly "OPIA") - A Polymorphic z80 language
« Reply #142 on: July 26, 2012, 05:14:22 pm »
I have made all recently mentioned changes to the grammar!

 - Templated types (e.g. Foo<T:Printer> { ... } )
 - Extra "this" type for cofuncs.
 - No more embedding of funcs or cofuncs within structs or cofuncs (enforces clarity)
 - You can now be more liberal with the "dot" access (A.func(...), B.(*type), etc.).

Have a look :)
« Last Edit: July 26, 2012, 05:22:17 pm by shkaboinka »

Offline BlakPilar

  • LV8 Addict (Next: 1000)
  • ********
  • Posts: 734
  • Rating: +44/-1
    • View Profile
Re: Antelope (formerly "OPIA") - A Polymorphic z80 language
« Reply #143 on: July 26, 2012, 09:42:00 pm »
Lookin' good ;)

Quote
- No more embedding of funcs or cofuncs within structs or cofuncs (enforces clarity)

Does this mean we have to use C++ style method declaration? I.e.
Code: [Select]
struct Foo { ... }
func Foo::Bar() : byte { ... }

Offline shkaboinka

  • LV3 Member (Next: 100)
  • ***
  • Posts: 86
  • Rating: +10/-1
    • View Profile
    • Antelope (polymorphic z80 language)
Re: Antelope (formerly "OPIA") - A Polymorphic z80 language
« Reply #144 on: July 27, 2012, 04:38:48 am »
Does this mean we have to use C++ style method declaration? I.e.
   struct Foo { ... }
   func Foo::Bar() : byte { ... }

Yes, this has always been the case (so that you can add new methods you as needed, without affecting the "definition" of a struct). Allowing them inside of structs was something I had added for familiarity with Java/C# as well. However, since you can embed function pointers inside structs (and make them act as virtual methods as well), I thought it would minimize confusion (and simplify the language) to just stick with one convention:

Code: [Select]
struct Foo {
   func(*Foo,...) fp; // embedded function pointer
   func(this,...) mp; // just like fp, but counts as a "method"
}

func f(*Foo,...) { ... } // function (not embedded in Foo)
func Foo.m(...) { ... } // method (same as f, but is a method)

Foo foo, foo2;
foo.fp(foo,...); // foo EXPLICITLY passed as 1st arg
foo.fp(foo2,...); // same deal (can pass ANY Foo)
foo.mp(...); // "method" = foo IMPLICITLY passed as 1st arg ("this")
f(foo,...); // same deal as fp, but not a func-pointer
foo.m(...); // same deal as mp, but not a func-pointer

// They all STORED as func(*Foo,...) though;
// and since fp and mp are pointers, you can do this:
foo.fp = foo.mp = f; foo.fp = foo.mp = m;

I honestly prefer the "inside" form myself; but it's not hard to get used to, and you have the following benefits:
 * The struct JUST contains what it actually consists of (e.g. it "is" its "constructor"; the end).
 * This is simpler than in C++: You use "." insteaf of "::", and only declare it once (no "headers").
If enough people are not appeased by these benefits (and my other reasoning), then I might reconsider. I do want to make a language that people like as well.
« Last Edit: July 27, 2012, 04:25:16 pm by shkaboinka »

Offline shkaboinka

  • LV3 Member (Next: 100)
  • ***
  • Posts: 86
  • Rating: +10/-1
    • View Profile
    • Antelope (polymorphic z80 language)
Re: Antelope (formerly "OPIA") - A Polymorphic z80 language
« Reply #145 on: July 29, 2012, 11:48:09 am »
*Bump* - I have made changes to the Overview, with the following changes:

 - Tuples can now have empty items (e.g. use "(,x,)" instead of "(_,x,_)").
 - Added a detailed section just for namespaces.
 - Changed the preprocessor directives to be more traditional
 - Elaborated on how default values work (not the same as in other languages!)
 - Gave example of how anonymous entities in a namespaces take on the namespace name.

Offline BlakPilar

  • LV8 Addict (Next: 1000)
  • ********
  • Posts: 734
  • Rating: +44/-1
    • View Profile
Re: Antelope (formerly "OPIA") - A Polymorphic z80 language
« Reply #146 on: July 29, 2012, 12:07:00 pm »
Cool. I like the preprocessor directives especially since I'm starting to understand their usefulness (remember, I have mostly confined myself to C# where they are largely unnecessary). Do you plan to add #pragma, or would that not be needed?

Also, with the outside struct method declaration, I don't mind it. I would more than likely keep structs in their own files, though, then.
« Last Edit: July 29, 2012, 12:07:44 pm by BlakPilar »

Offline shkaboinka

  • LV3 Member (Next: 100)
  • ***
  • Posts: 86
  • Rating: +10/-1
    • View Profile
    • Antelope (polymorphic z80 language)
Re: Antelope (formerly "OPIA") - A Polymorphic z80 language
« Reply #147 on: July 29, 2012, 05:08:35 pm »
Cool. I like the preprocessor directives especially since I'm starting to understand their usefulness (remember, I have mostly confined myself to C# where they are largely unnecessary). Do you plan to add #pragma, or would that not be needed?
No pragma, because I intend on having my compiler be "the" compiler for Antelope (like how java uses "javac" as the core compiler, but various IDE's can be built off of it). Instead, by exposing the guts of the compiler as an API, other tools can "intercept" code between phases and do whatever else they want (including intercepting a "#pragma" anyway).

Also, I will have the compiler strictly allow each file to be used only once, with context of usage not playing any significant role (other than referring to relative file-locations). However, I'd like to allow URL's in #includes too! :) I will probably let the compiler take an argument to specify default url paths as well as default file paths, so that other tools (IDE's) can provide environment control separately from the source code.

On a side note, "projects" could be managed simply enough by creating one file with an "#include" for everything else; and since this can be done with "#define" as well, perhaps it might be worth providing a "#nodef" or "#ignore" directive to veto specific things beforehand.

Offline shkaboinka

  • LV3 Member (Next: 100)
  • ***
  • Posts: 86
  • Rating: +10/-1
    • View Profile
    • Antelope (polymorphic z80 language)
Antelope - New Syntax for OOP Idioms!
« Reply #148 on: August 09, 2012, 04:19:58 pm »
NEW SYNTAX FOR OOP IDIOMS!

To test the feel of everything, I compared a simple OOP example from Java/C# with the "equivalent" Antelope code, and found it to be bulky and nasty. Therefor, I am wanting to rework some of the syntax relating specifically to "inheritance" idioms, as follows (with "..." indicating "no change"):
Code: [Select]
PSUEDO-JAVA/C#        | | ANTELOPE (new syntax) | | ANTELOPE (old syntax)   
========================================================================
class Fooer {         | | struct Fooer {        | | ...                     
  void Foo() {        <1>   func Foo() {        <1> func(this) Foo = func(this) {
    Print("Foo ");    | |     Print("Foo");     | |   ...                   
  }                   | |   }                   | | }                       
}                     | | }                     | | ...                     
                      | |                       | | ...                     
class FooID : Fooer { <2> struct FooID {        <2> ...                     
  int id;             | |   int id;             | | ...                     
                      | |                       | | ...                     
                      <3>   Fooer {             <3> Fooer = Fooer {         
  void Foo() {        <4>     func Foo() {      <4>   func(this) {         
    Print("Foo ");    | |       Print("Foo ");  | |     ...                 
    Print(id);        | |       Print(id);      | |     ...                 
  }                   | |     }                 | |   }                     
                      | |   }                   | | }                       
}                     | | }                     | | ...                     
1) I'd previously given the "func(this..." syntax for declaring function-pointers "as methods" (i.e. "f.Foo(f)" shortens to "f.Foo()"), which allowed "virtual" (late-bound) methods to be "simulated". However, letting these be declared directly as "internal methods" looks much cleaner, matches the common OOP idiom/paradigm, and still gives control over where they are stored in the struct.

2) Antelope rejects traditional "inheritance" in favor of composition. Instead, anonymous members provide transparent access to their internals, as if they were "inherited" directly into the containing struct. Thus, the "inherited" Fooer appears in the body of the struct rather than at the top (as in 3).

3) Since anonymous members exist soley to "simulate inheritance", it is just redundant to have to repeat the anonymous type twice when a more compact form will do. This form is suggestive of embedding an "anonymous class" within a struct, which is exactly the intent (as in 6).

4) Since "inheritance" is simulated by composition, the new "Foo" function must be embedded directly in the anonymous Fooer. However, the only safe option is to use a "Fooer" method (since a "FooID" method could have misalignment issues) declared anonymously (to hide it from "Fooers" outside of FooID) and then type-cast to "FooID" within the method. This new form takes a non-method function definition for overriding (replacing) "inherited" methods, which then does these things automatically (using offsets if necessary). These can be given anywhere in the initialization list, and are skipped if omitted.
Code: [Select]
PSUEDO-JAVA/C#        | | ANTELOPE (new syntax) | | ANTELOPE (old syntax)
======================================================================
Fooer foo = Fooer();  <5> Fooer foo = Fooer{};  <5> ...                   
FooID fid = FooID(5); | | FooID fid = FooID{5}; | | ...                   
                      | |                       | | ...                   
Fooer moo = Fooer() { <6> Fooer moo = Fooer {   <6> ...                   
  void Foo() {        | |   func Foo() {        | | func(*Fooer f) {     
    super.Foo();      | |     Fooer.Foo(this);  | |   Fooer.Foo(f);       
    Print(" Moo!");   | |     Print(" Moo!");   | |   ...                 
  }                   | |   }                   | | }                     
}                     | | }                     | | ...                   
5) Initialization values are given directly "{...}" rather than via a constructor "(...)". The first variable declaration can be simplified to either "Fooer foo = {5};" or  "foo := Fooer{5};" for conciseness.

6) "Inner" methods are normally skipped in initialization, but new versions can be defined for them (in any order) (as in 4). This very closely resembles an "abstract class" declaration in Java, which likewise creates an instance with "new" methods (only the Antelope version does not require a "new version" of the struct: this is merely a regular initialization which passes an anonymous function to the function-pointer "Foo").
Code: [Select]
PSUEDO-JAVA/C#               | | ANTELOPE (nothing new here)
=============================================================
void FooFooer(Foo f) {       <7> func FooFooer(*Foo f) {     
  f.Foo();                   | |   f.Foo();                 
}                            | | }                           
                             | |                             
FooFooer(foo); // "Foo"      <8> FooFooer(foo); // "Foo"     
FooFooer(fid); // "Foo 5"    | | FooFooer(fid); // "Foo 5"   
FooFooer(moo); // "Foo Moo!" | | FooFooer(moo); // "Foo Moo!"
7) In Java/C#, pointers ("references") are automatic. In Antelope you have to use them specifically (and you should, because they are more efficient).

8) The compiler will automatically pass the address of these variables so as to match the type. Otherwise, this code is exactly the same. Alternatively, FooFooer could have been written as a method ("func Fooer.FooFooer()").

NOTE: Although there were short forms (e.g. "Foo :=" rather than "func(this) Foo =" (at 1), or "Foo := Fooer{}" or "Fooer Foo = {}" (at 5)), I used the fully expanded forms for a more complete comparison of the fully written out forms.
« Last Edit: August 09, 2012, 04:24:56 pm by shkaboinka »

Offline shkaboinka

  • LV3 Member (Next: 100)
  • ***
  • Posts: 86
  • Rating: +10/-1
    • View Profile
    • Antelope (polymorphic z80 language)
Re: Antelope (formerly "OPIA") - A Polymorphic z80 language
« Reply #149 on: September 27, 2012, 06:21:01 pm »
(I posted this elsewhere a while ago) Potential Major Change:

TRAITS seem to solve most of the problems not solved (or caused) by inheritance (single or multiple, or using mixins). Traits are like interfaces, except you can provide default implementations for functions ("methods"). They would keep their original functionality as interfaces without any added bulk or framework, but with the addition that structs can "use" them and gain all their methods for free! This provides a clean model for code reuse without affecting any underlying framework!

Code: [Select]
trait Wargler {
  Foo(int);
  Bar():int;
  Wargle() { Foo(Bar()); }
  Talk() { Print("Wargle!"); }
}

trait Fargler {
  Boo();
  Bar() = Moo(5); // { return Moo(5); }
  Fargle() { Moo(Bar()); }
  Talk() { Print("Fargle!"); }
}

struct A : Wargler { ... }
struct B : Fargler { ... }
struct C : Wargler, Fargler { ... }

In this example, structs A, B, and C use above traits, causing the implemented methods (functions) of those traits to be automatically added as (static) methods of those structs. The struct is required to implement the other methods itself (and may also re-implement any of the others). Multiple traits can be combined (as with struct C), and implement each-other's methods (but if more than one trait implements the same method, then it must be redefined in the struct).

The following methods are required for this trait usage:

Code: [Select]
func A.Foo(int) { ... }
func A.Bar():int { ... }
func B.Boo() { ... }
func C.Foo(int) { ... }
func C.Bar():int { ... }
func C.Boo() { ... }
func C.Talk() { ... } // Overlaps both traits

The following methods are generated automatically from this trait usage:

Code: [Select]
func A.Wargle() { Foo(Bar()); } // func Wargle(A* a) { A.Foo(a, A.Bar(a)); }
func C.Wargle() { Foo(Bar()); } // ...etc...
func B.Fargle() { Moo(Bar()); }
func C.Fargle() { Moo(Bar()); }
func A.Talk() { Print("Wargle!"); }
func B.Talk() { Print("Fargle!"); }

Traits still work like interfaces:

Code: [Select]
func Warg(Wargler w) { w.Wargle(); }
func Farg(Fargler f) { f.Fargle(); }

(a,b,c) := (A{...}, B{...}, C{...});

Warg(a); // wa := Wargler{a}; Warg(wa);
Farg(b); // ...etc...
Warg(c);
Farg(c);

Traits can build off of each other in the same manner (e.g. trait X : traitY { ... }). Trait methods can also be aliased to avoid collisions or link to different methods (e.g. "struct X : Wargle(Other = Foo)" gives "Other" as an alias for Wargle's "Foo" method within struct X).

Note: I have also added that syntax as a short-hand for a function body which just returns a value (i.e. "= value" rather than "{ return value; }").
« Last Edit: September 27, 2012, 06:22:06 pm by shkaboinka »