Jump to content
  • Advertisement
  • entries
    359
  • comments
    237
  • views
    190089

About this blog

Current topic is the development of a toy language to test a few syntax ideas.

Entries in this blog

 

Tangent: Yield, part 3

Lots of time tonight to work on Tangent and get yield working. Lots of trying to follow spaghetti stack in a debugger to see where and how yield methods don't work. My brain hurts...

I have yield working in source, sort of. If you try and yield from inside a nested block, and then re-call the method, and there's local variables used in the sub-block from a higher scope (but only if used after the yield statement)... then it breaks at runtime. I know vaguely where the bug is, but am at the moment incapable of tracking state as the various yield blocks go on and off of the stack.


Example code for the night:


public yieldsstring> Cow(){
yield("moo.");
yield("moo!");
yield("MOO!");
}

public static void main(){
foreach(string word in Cow()){
print word; // moo.moo!MOO!
}
}




If you change the 2nd yield to a return(); you only get moo.

Tidbits of progress.

Telastyn

Telastyn

 

Tangent: Yield, part 2

yield sucks. I mean it's good and all, but implementing it is deceptively tricky. There's a few ways to do it, and they're all (from as far as I can tell) about the same level of difficulty/trickiness. And none of them really fit into the nice stack based execution frame of mind.

There's three main ways I've seen in my (limited) research/ponderings:

1. The thready way. yield is a special case of co-routines. Its natural then to implement co-routines via threads (or some emulating more lightweight mechanism). One thread calls the yielding method, which starts in its own thread until it hits a yield and then waits to be signal/pulse/awoke'd by a second call.

The problem here is that threads tend to be a little heavyweight for this; it requires the runtime to know about threads; and some mechanism to know when to join/terminate the thread (since the yielding method isn't guaranteed to reach its conclusion).

2. The C# way. C# does something a little funky to make yield work. The declaration itself creates an anonymous type where the parameters are member fields, and there's a 'return value' and a 'state' variable. It then generates a method based on the contents of the declaration which is mostly a big case statement and a bunch of jumps in-between it. A yield changes the 'state' variable which the giant case statement uses to effectively start the method at different points.

Slimy spaghetti to implement, but wildly elegant since it doesn't require changes to the stack execution model to get the behavior. The problem is that it requires case statements and jumps to spaghetti it, and Tangent's runtime has neither (directly). It could be done with a series of methods and a delegate, but that's similarly icky.

3. The naive way. Conceptually yield works by pausing execution and returning control to the caller without resetting the state of the method. Naively it should be possible to reproduce that directly. Keep the bits of bytecode around with their execution pointers. A second call just starts them back up where they left.

The problem here is (like many naive things) the implementation details are a bit more complex than the conceptual details. You can't just pause everything, the top most stack frame needs to increment the execution pointer 1 before returning control. And you have to consider that these things might be nested or two yield methods might exist for one caller. Plus 'restarting' at an arbitrary depth without a whole lot of if(yield){}else{} in everything that ever gets invoked....


In the end, the naive way best fit with the runtime's design. I made a subtype of Block with the modified behavior and added state necessary for a yield. Here's the simple test app that hand-crafts a yielding block. It gives a little insight to the structure of the underlying 'bytecode'. Next in the queue is making code generation so that Tangent syntax can effectively reproduce this.


namespace YieldTest1{
class Program {
static void Main(string[] args) {
Tangent.Lang.Object.GenerateDefaultRoot();

YieldBlock TestYieldBlock = new YieldBlock();

//
// {
// yield 1;
// yield 2;
// yield 3;
// }
//

TestYieldBlock.Statements.Add(new Yield(new IntLiteral(1)));
TestYieldBlock.Statements.Add(new Yield(new IntLiteral(2)));
TestYieldBlock.Statements.Add(new Yield(new IntLiteral(3)));

TestYieldBlock.MethodInstance = new Tangent.Lang.Object(Tangent.Lang.Type.Method);
TestYieldBlock.MethodInstance.Members["ExecutionBlock"] = new Tangent.Lang.dotNetObject(Tangent.Lang.Type.YieldBlock);
(TestYieldBlock.MethodInstance.Members["ExecutionBlock"] as dotNetObject).StoredObject=TestYieldBlock;

while (!TestYieldBlock.Done) {
TestYieldBlock.Invoke(null);
if (!TestYieldBlock.Done) {
Console.WriteLine((TestYieldBlock.MethodInstance.Members["ReturnValue"] as dotNetObject).StoredObject);
}
}
}
}
}






1
2
3

Telastyn

Telastyn

 

Tangent: foreach.

I finished a working solution to the nested block variable access bug. Probably not the best solution, but it works. As you might expect by the name, variables declared more than two blocks deep now don't cause runtime errors.


That also means C# style foreach now works:


public static void main(){
local Listint> foo = new Listint>;

foo.Add(1);
foo.Add(2);
foo.Add(3);

foreach( int x in foo ){
print x; // 123
}
}





A functional style foreach will have to wait until method generics work a little better. Next on the list is making yield work. Maybe tonight, probably not.

Telastyn

Telastyn

 

Tangent: foreach beginnings

As usual, I'm not as bright as I think. the yield stuff ran into a roadblock because the vague design proved not viable for anything beyond the simplest case. And it's not the sort of thing that you can just look up... Full coroutines are not the sort of thing I want (or rather want to dedicate the time to implement well), and everything else is how to hack the behavior using goto or exceptions or C macros.

So I went to work on foreach. The thread about for loops in the last posting was actually pretty insightful. In general people seem not to care for the C style loop so much. And while it might be interesting to leave such a construct to the library, I think it's something that's used enough that it should be dealt with.

"Make the easy things easy."

So, foreach will exist at least as a statement; mostly the same as C#:


foreach ( in ){...}


It's slightly unlike C# in that it is built after generics. The IEnumerable expression must be of a compatible type to the variable; I'm not auto-casting from Object.

The parser and code generation worked pretty well with minor bughunting. Unfortunately, I ran into IEnumerable importing problems (since fixed) and bug #14 (the runtime doesn't generate local variables within sub-blocks). So it looks like I'll need to get to that bug a little before I anticipated.

That'll be a bit of work, but should then allow this to work as expected. Then on to yield completion and a few example programs. By then I'll probably need to do some backfill on phrases and generics where I missed some stuff.

Telastyn

Telastyn

 

Tangent: Yield, part 1

Not much Tangent work recently. Recouping from work fires and rebuilding social currency. Tonight though I got a little work in, moving along to the yield keyword/behavior now that iterators work. I added behavior to the block code to allow the yield behavior rather than requiring the method get reset each time; actually rather trivial based on how it was implemented.

I also modified the syntax for methods (and anonymous methods) to not just take a type expression as a return type. The syntax now allows for yields there. The method still returns IEnumerable like C# generators, but it's a little more explicit about what is going on (and happens to make my life easier since I don't have to inspect the entire method body to know if I need to generate the anonymous enumerable or not).

Also, I'm looking to make yield return X into simply yield X. I've not decided yet if I need/want yield break but will probably make return(); do that behavior if desired. So, example expected implementation:


public static yields from (int i) to infinity{
while(true){
yield i;
i++;
}
}

for each from 0 to infinity do (int x)=>void{ print x; };


And hopefully getting some type inference or perhaps making no lambda return type mean void return to make the end a little less verbose.

Telastyn

Telastyn

 

Tangent: Enumerable interface, part 3.

The battle versus IEnumerable has been won!

The type (and IEnumerator) imports nicely now. It's nicely specialized within List, and is all nice and typesafe. The following example code compiles and runs nicely:


public static void main(){
local Listint> x = new Listint>;

x.Add(1);
x.Add(2);
x.Add(3);

local IEnumeratorint> itr = x.GetEnumerator();

while(itr.MoveNext()){
print itr.Current; // 123
}

}





Oddly enough, none of the problems with it were the sort I expected; and didn't have much to do with the specialization of the generic and non-generic bits. The two major bugs were that I neglected to care about explicitly defined interfaces, and I was being dumb about how I new'd dotNet types.

I would generate the object for the .NET type and then store the value in it based off of the return of the invoke. This caused problems for abstract types/interfaces, since they can't just be new'd as is. As a bonus, that should cut down on the number of allocations during common use.

Next in the queue is almost certainly implementing the for loop now that this stuff is done. Not quite sure how that's going to go, so visit the thread about it, and put in your two cents. Damned laze-abouts...

Telastyn

Telastyn

 

Tangent: Enumerable interface, part 2.

Another night, another hour of Tangent work. FlattenHierarchy in GetMembers in the reflection libs seems to not work/apply to interfaces. I added something manually to flatten an interface to include the interfaces it inherits. IEnumerable and IEnumerator now import as they should. List::GetEnumerator gives me crap when I invoke it though because it thinks it's still generic.

That one will wait for another day.

Telastyn

Telastyn

 

Tangent: Bugs and Enumeration

I had a little time this evening to get back into Tangent work. Mostly I found bugs; or at least things that didn't work surrounding this-methods ( operator() overloading for those familiar with C++) and inheritance. And with methods in generic types. And with string importing...

So I filed in the bugs and went to work making sure Tangent can import IEnumerable/IEnumerator. I need them to make foreach work (or perhaps a Python style for). They're also pretty necessary for generators and to do a number of the little example programs I want to get focused on as the driving force behind language implementation rather than my whimsy.

At the moment, the importer gets the types (and ignores their ungeneric kin) and generates the Current property for the enumerator. The others fail out; probably due to generic referencing issues. ~3 more members to get working.

Telastyn

Telastyn

 

Bugs!

Took a quick look at Tangent tonight, managed to not do much more than open 6 bug reports. Good to keep such things documented, bad that I've been so scattershot in getting things done. I happened across a nice list of beginner style exercises. I think that will be a good reference for example Tangent code and stuff to work through to ensure everything is there and working.

Work is quieting down a little, so time should likely return for hobby work.

Telastyn

Telastyn

 

Bleh!

I wanted to get some Tangent work done over the holidays, but the star-crossed work project from hell arose from its state of half-death to suck more time and energy. That and the usual social niceties prevented me from doing anything more than design work while driving about.

I realized though that I can't do much more with the Units of Measure now. The rest of their stuff (mostly) needs to be done in source rather than as part of the language. And that requires class templates to be there and working. Not there yet. Probably for the best; it'll get me back working on that stuff that's more important.

The key things yet to work on for the next release:

- class templates.
- templates as part of phrases.
- phrase re-working to group as much as possible rather than as little for phrase groups.
- verify type-parameters in phrases.
- add unit of measure construction to built-in numerics.
- add some sort of simple debugging support.
- possibly improve .NET importing.
- possibly fix the nested variable bug.
- provide a CLI compiler tool for linux people.

Telastyn

Telastyn

 

Tangent: Measurement infrastructure

I had some time tonight to work on Tangent. On the menu? Infrastructure for unit of measurement types (as the clever folks might've guessed by the title). Under the hood, a Measure inherits (awkwardly) from Type. It adds a list of UnitOfMeasure, a comparator, and a few util methods for now. A UnitOfMeasure contains a Measure and an exponent (in numerator/denominator form rather than float).

Type operators already exist for the usual suspects. They need to be extended to be aware of the new subtype (did I mention this was a little awkward?). The equality and subclassing operators actually didn't need to be modified; since measure types are goose types, that works itself out. Same with tupling, and intersection. Bind and select (to bind a generic param, and select a method from a group respectively) don't really apply. Union (inheritance) is a TODO [eventually, you'll be able to inherit from classes without data fields]. So umm, not much work needed here.

But measures need a few more operators. Type operators to construct new types from the existing types. The three here are multiplication, division, and exponentiation. They have 2 key jobs. First is to do the math on the exponents of the Measures. The second is to do the type fiddling with the 'value' of the measure. int of meters/int of seconds should yield float of (m/s) for example. To do this, the operators will look at the implemented multiply and division methods for the numeric types and use that type for the determination. That might run into problems, but should be an okay way to keep the things generic enough for user numeric types.

So, for those who followed last post's link or know of the F# implementation, this is going to be a little different. Instead of float, meters will default to float (or more accurately be declared as measure meters{}) so the use of the measure will be a little less verbose/more focused. Of course a different type might be generated... say meters with a complex unit type if its manipulated that way. In general though, I want the programmer to be able to use what numeric they need; not annoy them with conversions or accommodations just to get measure safety.

So, the night's test code:

Measure Meters = Measure.Create("Meters");
Measure Seconds = Measure.Create("Seconds");
List mps = new List();
mps.Add(new UnitOfMeasure(Meters, 1));
mps.Add(new UnitOfMeasure(Seconds, -1));
Measure V = Measure.Create(mps);

Console.WriteLine("[m,s^-1] name: {0}",V.Name);

Measure v2 = Tangent.Lang.Type.PerOperator(Meters, Seconds) as Measure;
Console.WriteLine("m/s -> {0}", v2.Name);

Measure a = Tangent.Lang.Type.PerOperator(v2, Seconds) as Measure;
Console.WriteLine("mps/s -> {0}", a.Name);

Measure a2 = Tangent.Lang.Type.PerOperator(Meters, Tangent.Lang.Type.ExponentiationOperator(Seconds, 2,1)) as Measure;
Console.WriteLine("m/(s^2) -> {0}", a2.Name);

Measure m = Tangent.Lang.Type.MultiplyOperator(v2, Seconds) as Measure;
Console.WriteLine("mps * s -> {0}", m.Name);





[m,s^-1] name: Meters per Seconds
m/s -> Meters per Seconds
mps/s -> Meters per Seconds^2
m/(s^2) -> Meters per Seconds^2
mps * s -> Meters


Eventually I aim to get aliases into measure definitions, probably with certain names marked as singular/plural/abbreviation to generate better names, and to give the option of a terse 'abbreviation-style' name.

Telastyn

Telastyn

 

Tangent: Measurement intro

First off, thanks to Daerax for the great link that helped this post get along.

Secondly, w00t! I got my christmas present

And work is finally calming down after release and the clean-up of a monumental clusterfuck caused by a single mis-labeled constant used once in code (not mine). Which allowed me a little time tonight (and more time going forward) to fix Tangent bugs and bemuse myself.

A note about syntax and a minor bugfix:


public void foo( arg){
//...
}


now works. foo takes one argument which must be at least a string, and stores the type into generic T. If string is omitted from the above declaration, foo will take any type.


Tonight though I started into quickie work on units of measurement for Tangent. I talked about this a little a few posts back, and Daerax's link helped remind me of schooling in the dusty parts of my mind. I'm not looking to use them so much for calculation, but more to disambiguate int into '# of _'.

I'm thinking that a little bit of this operation should be pretty easy (famous last words, I know...). Tangent already knows the concept of Type Expressions. Adding some for the measures to represent measure multiplication, division, and exponentiation should be easy. Tangent also knows fairly arbitrary overloads for types. Adding something to int/float/etc. that takes a measure and returns (int & measure) should also be pretty simple.

By the weekend, I'd like to get simple measures done. x meters is different than x feet. Then complex measures. Then measures in syntax. Then conversions. Then probably some cleanup/finishing (perhaps inferring/constraining on exponents).


So, the feature in a nutshell:

- A measure is a distinct type element; similar to an enum that it's not a record of data but instead a differentiator for a single element of data. They'll be declared similarly to a class, but use a different keyword.
- Their instances will be value types with the number as the only data.
- Extra methods and static data may be declared on the measure.
- Likely need a special conversion syntax to ensure reflexiveness of conversions.
- Likely need aliases to make meter, meters, m, metre... work.
- All measures will act as goose types.
- Measures will probably be constructable only via a # + measure type -> measure instance style. (10 seconds; 9.88 m/s^2; 1000000 usd;)
- Will probably need some caching/intelligence to make sure that a constructed type uses a declared type if it exists (eg. if you define methods for a newton and then via operations construct a (kg m/s^2) [a newton], those methods should be available)
- Likely will allow a generic parameter to define the numeric that represents the value, stealing the conversions from the numeric types (int/int -> float; int meters/int seconds -> float m/s). Too bad I'm not doing this from scratch and can't just use arbitrary precision numbers as default.

More to come as it's implemented.

Telastyn

Telastyn

 

Introspection

I realized something today. I do not learn advanced concepts well. This is a known problem. There's probably a lot of reasons why this is, and I have a decent idea about a few of them, but today I thought of a new one.

I am pretty smart. Or at least all the tests always said I was, and looking around for the past 20+ years affirms that. The key problem I've had for some time is that I might be a little too smart, and it's led to problems later in life.

I remember when I was young my father (a mainframe programmer) would get calls late at night when a program went bad. He'd sit there, right after being awoken in the middle of the night without a computer and quote line numbers to the guy on the phone regarding what code to fix. My childhood memory wasn't quite that good, but spelling tests are easy when you can recall the image of the word in the spelling book.

The other thing that made schoolwork easy was the ability to pattern match and do kind of extrapolation. A sentence's grammar is something of this type followed by a different type, followed by... fits mentally right in with algebra behaviors. And that's usually how I'd learn new things. The new thing follows this pattern, or the new thing behaves like this known thing.


So these sort of things were great in school. Most of (US, public) school is just memorizing and regurgitating things. The rest is learning pretty similar concepts in a vaguely different form.

Now as an adult, they're kind of sucky. My memory is mostly gone. I'll remember the shape of the license board in FFX or where we left off in the last D&D campaign or the ID of the user I worked on yesterday or who the Steelers quarterback was when they tied Atlanta... but not reliably anymore. And code? No way. It doesn't fit into the shape or pattern or however my memory likes it.

And as it turns out, mapping new concepts into old concepts only goes so far. It goes a hell of a long way mind you, but eventually you need (a few) new concepts to really know something different. And it's hard to get them when you've never needed to.

Perhaps more significantly, I have problems communicating certain things to people. I'll tend to describe coupling oddly for example. That the data organization should line up like a tree; just as actual personnel organizations and areas of responsibility; just as a graph of sciences and their interactions or a game's tech tree. I tend not to make any distinction between any of them, and the abstract concepts of relations, interactions, parents, 'weak' sort of ownership are all still sort of there even if they're not commonly used in that particular tree. And that leads to problems sometimes when other people just don't think of them that way.


Anyways, as I was saying, I realized something today. Another facet of learning problems I have is when people abstract stuff for me. I was looking at the wikipedia entry for Limits in category theory and my brain shut off. Not exactly surprising. But this time I realized why I hate greek lettering and the general 'blah is defined as blah op blah to blah blah blah op' that is universal to higher math resources and not a few programming language books/articles/guides. I can't conceptualize greek.

I just can't form a symbol in my head to represent the first element of the relation/equation. X is fine. I know what X looks like, what it sounds like, and reading it I can get a handle to 'some placeholder named X'. Greek? Can't do it. Things that I'm not sure are placeholders, or are some new abstract concept I don't know? Can't do it.

Further, I don't learn by taking an abstraction and specializing it to cases. I learn by taking hard examples (which give me nice solid things to use as symbols/images while thinking) and extrapolating them into the general case. So compare that wikipedia article to this haskell tutorial regarding list comprehensions. That tutorial is what kinda set off the light bulb for me today. It was the first thing I thought of after my brain rebooted. It's the same sort of math gibberish I have problems with, but it starts with an example. No problem at all learning what it is and even the mathematical notation it's based off of.

It starts with a nice set as the visual symbol and then edits it as the tutorial goes along describing what the example comprehension is/does. It describes what everything is in the example, and relates it back to the mathematical roots so that mentally I can make that footnote. It then does progressively more complex things, but always again from an initial state. It works fantastically for me; someone who starts with a known concept and maps new concepts to it, expanding or adapting the original concept as needed.

Telastyn

Telastyn

 

Tangent: int sucks.

Work continues to be hellish. If someone does not go postal or quit or end up in a psych ward or shot by an angry client by the end of next week, we'll call it a win.


So no Tangent work. Still need to implement better method grouping for phrases, generic bits for phrases, the type-arg sort of stuff, and constraint inference. After that though, I'm considering making less ints.

Less ints you ask? Yes. Not the 4-byte, 8-byte, 16 byte, etc ints (though there should only really be big int, native int and compatibility), but something to promote not using ints everywhere. There was a post here where a beginner couldn't use trig functions because they failed to realize that it took radians. Any sort of physical modeling will use lengths and sizes; most should be in meters, but what if they're not? And why do they need to be specified in meters rather than km or cm for different models?

And the same sort of problem exists with time values (is that 4pm local or gmt?), with currencies (usd, ausd?)...


Sure, in languages that allow it, people could declare a type that acts like a number but is restricted explicitly to be n seconds or meters or goats. But nobody does, because it's either tedious and sucky or because it's too slow or because it causes problems when interacting with other stuff. And nobody will really implement them with any consistency. And you run into problems about ownership of the certain data. If you have 100 goats here and 200 goats there, does there need to be two instances of goat data? Can that be static?

So I think that it would be cool to make that not tedious so it fits into idiomatic Tangent. Something that is consistent and intuitive. A feature that gives enough benefits over annoyances that people actually use it rather than ints everywhere.

The key sort of thing I'm looking to gain is:

set AlarmClock to Now + 5 minutes;
AlarmClock = Now + 5 minutes;
// versus
set AlarmClock to Now.AddMinutes(5);
AlarmClock = Now.AddMinutes(5);

x = sin( 30 degrees ); // distinct types can be overloaded more cleanly
x = sin( 1.2 radians );

local meters distance = 30 cm; // via some auto-conversion table.

Ogre gains 50 strength; // can go into a container of stats rather than
// int strength;


There are a few problems that are looming about. Certain things should be just ints. Some are acceptable as floats. Some probably even more exotic numbers. Some make no sense as negatives... how to deal with that. How to deal with overloading. Tangent tends to allow people to re-use identifiers; overloading behaviors. Should the name of the label follow that or be kept distinct. How to deal with classifications of these things (having a currency type that can hold usds or euros). And perhaps most far-reaching is that the best way to implement this sort of thing is to make int a method that takes either a type param or an identifier and returns the new typed int. That may yield some unexpected results (or uuuuuugly misuse) in some scenarios.


Still, something I'm thinking about. Can't find anything at first glance regarding language support elsewhere for this sort of thing. I would've thought that it would've been tried or at least speculated. Probably the best line of thought would be to look at C++ and how the things would be implemented there; what the flaws or annoyances were. Y'know, if my google-fu continues to suck.

And hopefully time and motivation will return to get the 4-6 things ahead of this puppy on the list.

Telastyn

Telastyn

 

Ugh.

So that project that I thought would clear out once released? The hits just keep on coming with that. The project involves linking some payment processing with a new peer. First the peer neglected to implement one of the codes we needed. There's a bunch and it's a rare code, so we didn't catch it in testing. Unfortunately their company is made from red tape. So instead of doing a db insert (that takes a change control meeting and a 4 week release cycle), we had to re-number the id on our clients. Some 500,000 updates.

Then we were told that sending two files to their server for processing (with the same name; all their other files have names with datestamps) doesn't actually overwrite the last file. Some magic they've hacked into sftp (more likely cron) places the files into a queue. And then when processed, they're only processed one per day. No, processing all the waiting files would make too much sense.

And then today I find that my coworker botched the query for some account info. Is probably going to cause insufficient fund penalties for some 2000 people.


Great work on my first semi-major project lead; got some work to do it seems. Might've been easier with, I dunno... project managers or QA people. Still, my fault for not demanding them (even though the company doesn't really have any on staff).

At least all my actual code works without issue.

Telastyn

Telastyn

 

I am dumb.

Umm yeah. This is not the case. Today's lesson at the school of hard knocks is 'how much work is there once the work is "done"?'.

Telastyn

Telastyn

 

Tangent: Typeclass preview

Got to work over the weekend to babysit and tweak some stuff regarding the release of the big project I was leading. No time for work on Tangent, but hopefully the release will free up my motivation and time to get back to it.

And now that at least the basics of generics are declarable (and making them usable is a short step down the road) I need to start thinking about making constraints better. Or more practically, how to solve the 'numeric' problem with C#.

During my original design, I thought about allowing free functions to be declared from within a class. That would keep the tidbits alongside the class, would behave a lot like operator overloading does now; and most relevant to this discussion was the ability to declare them abstract. They'd then act as a typeclass requirement that would let the programmer know that the operator is (or must be) supported, but isn't done there.

But I'm leaning away from that now. It doesn't solve too much; rather it has the same problems with preference that operator overloading does, would require a lot of work to interpret position and extrapolate that to usage. And in the end it seems like it'd involve too many special cases, too many exceptions, and it'd still be limited. It would be easier I think to work on the visibility stuff so that free functions can be declared alongside the class but still have access to internals if needs be.

Instead I'm looking more towards inferred constraints on the method. No need to extrapolate what's good, and if the method uses the form declared within the class. Here the method would essentially defer compilation until bind-time. If the compilation of a statement fails with that particular type, it can dump an error saying 'hey this statement doesn't work with type X'. That'll usually happen at compile time unless some reflection business is going on. Great.

This is particularly great for lambdas. It'll allow you to omit the parameter types like C# users will expect, and keep static typing goodness. For more lengthy or traditional methods though, it'll kinda suck. The parameters will exist as only identifiers, which is terrible for readability. Goes directly against a lot of the other language tendencies. Especially for well known typeclasses like 'numeric', supplying an interface type is super helpful so the programmer doesn't have to hunt through a method body to see what needs to be supplied.


So my first thought was to take the inferred constraint idea and move it to classes. Programmers could make a method that caused the requirements to be shown:


class Numeric{
require{
local T a,b,c;

c = a+b;
c = a-b;
c = a*b;
c = a/b;
//...
}
}


Rather quickly I realized that such a construct is inviable. What compiles within this scope does not necessarily compile in a different scope. It can't follow the usual inheritance rules since stuff that returns a T might not return a T+U.

So what if we didn't allow T as an lvalue, and require the same sort of scoping... Yet more exceptions and special cases.


But what if I made it more like the usage constraints? They don't suffer from scoping issues, because they're specific to the method in question. They don't suffer from the inheritance problem because they're not inherited; more accurately they don't suffer the problem because all of the unification is done before doing the capability check.

So what the tentative plan is now is to use a similar construct, but to make it (mostly) distinct from classes. So, a type class can declare a block of statements that the type must correctly type-check for in addition to the usual elements of a class. The usual elements behave similarly to C# constraints or Tangent interfaces. Type classes may inherit from other type classes. The usual elements follow the same behavior; the requirements exist concurrently. To satisfy the interface, a type must satisfy all of them. When checked, the statements use the scope of their use, not the scope of their declaration.

or (with use of some psuedo-code for the typeclass and the includes):

// scope A
public type class Printable{
require(T x){
print x; // compiles fine. We don't care what print is here.
}
}

// scope B
include scope A;
public class Pirate{
public string Name = "Blackbeard";
}

public void show(Printable target){
print target; // Compiles nicely, print (local T _) is on the list
// of things guaranteed by the interface.
}

show(new Pirate); // Compile error, print does not type check.
// thus the param doesn't satisfy the interface.

// scope C
include scope A;
include scope B;
public void print(Pirate target){
Console.WriteLine(target.Name);
}

public void ConsoleDump(Printable target){
print target;
}

ConsoleDump(new Pirate); // great. print type-checks at this scope.





Seems like a better way to provide a 'Numeric' interface and similar constructs. It also seems like it'd be kosher and unambiguous. That said, I'm probably going to take a good hard look into how Haskell and Scala deal with this sort of thing at the very least.

Feedback, links, suggestions, problems?

Telastyn

Telastyn

 

Umm?

I am not sure if I should be insulted or thrilled.

Telastyn

Telastyn

 

While I think...

Plenty of time tonight to work on implementing generics for methods. I managed to get type parameters working earlier in the week. Again, that is this sort of construct:


public static bool foo {
// ...
}

if( foo string ){
// ...
}


A declaration that says 'this argument must be a type, and is referenced by identifier T'. Right now only single argument methods work, but eventually you'll be able to use type parameters as elements of a phrase declaration. I anticipate key uses being type operations like is/as, factory interfaces, and serialization sort of things.


Generic parameters to methods unfortunately has run into a snag. A snag I've run into 1-2 times before, so it's noteworthy enough to post as a warning to others. Plus, it helps me to think by writing about the problem.

Currently, generics are being implemented via a generic parameter which lives on a type, and a generic reference which lives... where-ever a type can go. When a type parameter is bound to a generic type, the generic parameter moves from the unbound list to a bound list (along side the type that is bound there) and a new type is returned with all the references replaced with the concrete type.

For generic methods where the type is inferred, the references serve as 'holes' for the inference to take place. The generic method (or type in certain cases) is overlaid on the concrete type. What you see through the holes is what is used for the generic parameters. To do that programmatically, the inference method starts at a point and walks along each method looking for the generic references. If there's a reference for the method it's inferring for, it looks at the concrete type and adds that to what it sees for that reference.

Once it's done, it binds what it finds to the parameters referenced. So:

foo( arg);
foo(new string); // T = string;

generic(T) foo(List arg);
foo(new List); // T = int;

foo( arg1, arg2);
foo(new string, new int); // T = intersect(string,int)
//
// (since it sees multiple sources, it generates
// the greatest common base class for them)


And all that works nicely when I hardcode stuff with the infrastructure. I'm just adding syntax at this point.

The problem that has come up more than once now is in 'If there's a reference for the method it's inferring for'. Mechanically, that means if the generic parameter in the reference is in a list of targets passed through the inference method. The list is made from the generic parameters on the method, and the references exist in the method definitions for the signature. What happens is that they don't quite match. All of the data is the same; the name, the type-class, the generic type that owns the parameter... but not the references themselves.

So the inference fails, and the cause is somewhere I didn't reuse the generic parameter when making references. And of course they're all over the place since most everything uses generics for something. I could make the parameters into value types, but I did that with the types themselves and that turned out to be less useful than I anticipated (with its own set of problems). Mostly it's a matter of me doing debugging around this fragile construct. Bleh.

[edit: and not 15 minutes later I find the problem. I was being lazy and calling a factory twice rather than once and assigning the result in two places. So generics now work in declarations for 'normal' void foo( a, b, c){} sorta things. You can't actually use them yet since the name resolution ignores them... baby steps.]

Telastyn

Telastyn

 

Tangent: Generic Method Sneak Peek!

Had the evening free to work on added implementation of generics. Generic methods are pretty much a hojillion more times difficult to implement than their class brethren. To complicate matters a bit, I'm looking to modify the standard syntax a bit to better support phrases.

There are three things that I'm looking to do with Tangent generic methods that deviates from the norm:

- Generics defined alongside the variable.

This one is kinda straightforward. Instead of public void foo(T x, T y) the declaration will be public void foo( x, y). Should make reading the things a little easier, but it's mostly done to disambiguate the syntax. Multiple declarations of the same name are the same variable (as above).

- Constraints defined alongside the generic identifier

A little less straightforward, but again, hopefully something that's not too weird. This moves the simple type constraints within the angle bracket declaration. Example:


// C#
public void foo() where T:HasName { }

// Tangent
public void foo(){}


I'm not quite sure if I'll go with that syntax or more of the Identifier:Constraint style. Either way, keeping the simple stuff declared alongside the generic itself. Again, declaration closer to the usage should be a little more readable, but the gain is mostly in aiding phrase usage.

- Types as params

Speaking of phrases, I'd like to allow type parameterization in phrases more naturally. print foo to string instead of print foo.ToString() or even print foo to(). This will aid in consistency within the language, allow for more straightforward specializations, and aid in the psuedo DSL programming.

And combined with the way method groups pick specializations it should allow me to construct stuff like is and as or even parameterized constructors within the language itself rather than needing hooks under the hood.


But for now, back to work.

Telastyn

Telastyn

 

Tangent: Generics Part 1

At least the most cursory of generics now work in source:

public class foo{
public T x;
}

public static void main(){
local fooint> bar = new fooint>;

bar.x = 5;
// bar.x = "foo!"; // error, no assignment to int from string.
print bar.x;

local foostring> baz = new foostring>;
baz.x = "moocow.";
print baz.x;

local foo fooany = new foo;
fooany.x = "moocow.";
// print fooany.x; // error, print can't take 'any'
}




Now on to methods.

Telastyn

Telastyn

 

Hardware and next steps.

My main dev machine ran into some problems a few days back. Windows would flash and generally not be interactive, making it pretty useless. The login screen and alt-ctrl-del screen worked well, so I put the problem in with the video card/driver (since I'm using vista). A reinstall of the drivers didn't help, so next step was to replace the (~3 year old) card.

Got a random geforce 9800 w/512m. Can't really tell the difference except that this one takes an extra power connector, takes 2 slots and makes a different fan noise. The store also had 1tb drives on sale, so I got one to replace my ever full games drive. Merry Christmas!

And around the same time, my linux machine developed a little more serious problems. It's much older and been running in a drawer rather than a case for years now. I'm actually pretty happy it's lasted this long. I'll decide later if it's worth replacing or if it's easier to just put an ssh server on my windows box.


As for Tangent, work begins on the next bit of development. For the next release, I aim to have native generics working. Generic classes and methods at least. Ideally I'll also get constraints, generics in phrases, and some non-trivial type inference done. Probably in this next release will be more .NET type importing, and maybe type expressions.

Today's work allows generic classes to be defined, but they're not yet instantiatable and the members can't yet use the generic param.

The test target is to allow this definition of not to work:


public abstract bool not(bool rhs);
public Type.False not(Type.True rhs){ return(false); }
public Type.True not(Type.False rhs){ return(true); }

public bool(T) not( bool(T) rhsMethod ){
return(
(T rhs)=>bool{ return(not rhsMethod(rhs)); }
);
}

public bool(A,B) mix Type.BinaryOperator not( bool(A,B) mix Type.BinaryOperator rhsOp ){

return(
(A lhs, B rhs)=>bool{ return( not rhsOp(lhs,rhs) ); }
);
}




A bit ugly, but will allow not to act as a modifier on methods or operators that return bool. a not equals b for example would parse out nicely into the proper order of operations without having to manually implement the opposite of equals. not is the simple intuitive case.

Might need to work on some syntax to make it less fugly.

Telastyn

Telastyn

 

Tangent: v0.25!

Link

New Tangent release is out. The link is for the release page describing some of the additions since you could last toy around with the language. It still mostly sucks, but has some new and interesting things I've not seen offered elsewhere. Compliments are welcome; bug reports or suggestions for improvement are more useful.

Telastyn

Telastyn

 

Tangent: Const-cleanup.

I fixed up a lot of the brokenness caused by the const addition. The pi and maze testapps work again.

More importantly, these sort of things will now provide you with an error (and not cause ambiguity errors when paired with a valid option like properties often cause):


4 = 2;
int = string;
* = /;

main = ()=>void { return("moo."); };


Also, assigning to method parameters or the 'this' reference is now invalid.

Telastyn

Telastyn

 

Tangent: Constness

Tangent at last seems to have lost some steam. A distinct desire to play games and added workplace workload and a visit from the family have conspired to leave me with little time and motivation to work on the language. There's about a half dozen mildly hairy changes that should get done before the next release. The first I did manage to work on today.

The practical problem is that properties and other method groups are not so usable. They tend to come up with ambiguity errors when there shouldn't be. First step in fixing that is making normal methods not assignable. I had planned around making that just a type annotation. A type gets a token making it either const or a delegate (however the typing worked out to make things work like you'd expect), but it turns out things just don't work like that. The proper place to put a constness token is in the member definition (alongside the visibility level and 'virtual' token).

Unfortunately, that's a fairly major change since the member definition class touches a lot of places. And it broke some stuff. So next comes cleanup of that followed by more changes to get property = property to do the right thing (but allow actual property delegate assignment too).

Telastyn

Telastyn

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!