Skip to main content
GameDev.net gamedev.net
🔒 Locked

Request for Comments: Epoch Language, Release 11

Started by ApochPiQ Jul 8, 2011 at 6:42 AM 10 replies 4k views
Original Post
ApochPiQ
ApochPiQ
Release 11 of the Epoch Programming Language is now available.
Several months of bugfixing, feature addition, and general refinement has led the Epoch programming language project along the road from Release 10 to the currently stable Release 11. A brief and by no means comprehensive list of changes can be found in the release notes.

Download Windows installer (runtime and IDE)

Download project source code snapshot



I am interested in the feedback and opinions of the community on this project; as you may or may not know, the Epoch language was born out of a discussion right here on GDNet, some five years ago. It's been a rollercoaster of a half-decade since then, with a few false starts and plenty of growing pains. Above all, though, it's been a tremendous joy to work on, and I look forward to hearing your thoughts on the progress and direction of the project as a whole.

Release 11 marks the first release of a (minimally) functional IDE for the Epoch language, dubbed Era. The Era project is implemented in Epoch itself, linking dynamically against the Scintilla text editing widget and a custom C++ lexer for rendering syntax coloration in Epoch code files. Over time I intend to continue growing the Era project along with the language itself, providing extensive functionality and accessibility to rival commercial-grade IDEs for other languages. It is no doubt very embryonic at this point, but it reflects a promising start for Epoch: an operational Windows program is now in existence which is implemented on the Epoch foundation, and paves the way for future development of the language project as a whole.

Epoch is still very much an infant language, and lacks many features and a bit of polish. There is still much work to be done, and quite possibly a lot of refinement of the work which has already been invested. To this end, I'm looking for feedback in a few specific areas:

  • How do you feel about the syntax of the language overall? Any suggestions for changes or clarifications?
  • What features do you feel are most important to make Epoch a viable platform for your own development?
  • Where would you like to see future development effort focused?
  • What would it take to get you to start writing programs in Epoch, on any scale?
    Thank you for your time and interest, and again I look forward to hearing from the community.


    Enjoy!
newera
newera
Nice work on this project, very impressive. One quick question about the syntax:

For anonymous types, in your example you define the "anonymous" type as you pass it into the function:


entrypoint : () -> ()
{
foo(Test(42, "test"))
}



It seems a bit inconsistent with the primitive types, i.e., Test(integer(42), string("test")). This however seems overly verbose. Does the language support this instead?




entrypoint : () -> ()
{
foo((42, "test"))
}


Cool stuff!

Tony
ApochPiQ
ApochPiQ
Hmm... not sure if I garbled my terminology someplace or not, but there is no support for anonymous types in Epoch. The syntax you showed is for anonymous temporaries, i.e. values that exist only for the duration of the call to the function and are not accessible by name in the outer scope.

To elaborate, that example constructs a variable of type Test and no name, and passes that instance into foo(). This is subtly but importantly different from immediately constructing an anonymous type. I haven't found a use case for true anonymous types yet, but if you can convince me they'd be useful, I'd be happy to start thinking about how to hack them in :-)

Thanks for the comment!
newera
newera
Sorry, I misspoke -- I meant anonymous temporary variables. Temporary types sound like a flavor of tuples which could be interesting...

The point I was trying to make is that the type Test in the [color=#1C2837][size=2][color=#000000][font=CourierNew, monospace]foo[/font][color=#666600][font=CourierNew, monospace]([/font][color=#660066][font=CourierNew, monospace]Test[/font][color=#666600][font=CourierNew, monospace]([/font][color=#006666][font=CourierNew, monospace]42[/font][color=#666600][font=CourierNew, monospace],[/font][color=#000000][font=CourierNew, monospace] [/font][color=#008800][font=CourierNew, monospace]"test"[/font][color=#666600][font="CourierNew, monospace"])) [/font][font="Arial"]statement should/could be inferred, as the 42 and "test" are inferred. [/font][font="CourierNew, monospace"] [/font]
[color=#1C2837][size=2][color=#666600][font="CourierNew, monospace"]
[/font]
[color=#1C2837][size=2][color=#666600][font="CourierNew, monospace"]Tony[/font]
[color=#1C2837][size=2][color=#666600][font="CourierNew, monospace"]
[/font]
[font="CourierNew, monospace"][color="#666600"]
[/font]
ApochPiQ
ApochPiQ
Ah, I see.

The reason that this inference is not explicitly allowed at the moment is because of the planned support for generics. If foo() was generic on its parameter, there would be no way to reliably infer the type intended by foo((42, "Test")). However, I suppose that in general, when generics are not involved, there should be no reason to require the type annotation. I'll think about that tweak and see if it fits into the overall structure of things.

Thanks again!
newera
newera
Alternatively you could do something similar to what Scala does:


var(x,42) // inferred as an integer
var(y,42.0) // inferred as a real
Temp(temp, x, "test") // new instance of Temp

foo( var(x, "test") ) // inferred as type Temp



I'm not convinced this is any better, but interesting none the less.
ApochPiQ
ApochPiQ
Epoch believes in name-based type equivalence, not structure-equivalence. Therefore, suppose I have this code:

structure Foo :
(
integer(Alpha),
string(Beta)
)

structure Bar :
(
integer(One),
string(Two)
)

entrypoint : () -> ()
{
// Hypothetical "var" keyword in Epoch
var(x, 42, "Test")
}


Uh oh! Inference fails here, because we can't tell if Foo or Bar is the correct type.

Of course, we could decay back to a tuple instead of an aggregate type, and this solves the generics problem as well; but that requires 1) implementing a generic tuple mechanism in the language, which I've been trying to avoid thus far, and 2) introducing a nasty set of implicit type decay conversions, which I definitely want to avoid. Implicit type conversion is currently forbidden in Epoch, and I'm leaning towards keeping it that way.


[edit] What I mean here is that to keep things working uniformly, I would have to permit structures to implicitly decay into anonymous tuple types. I really don't like that idea, as nice as it might make some of the syntax. I'm a little too attached to the idealistic refusal to do type conversion without explicit casts, and if we demand a cast (or keyword/operator) to decay from a structure to a tuple, why bother with tuples in the first place?
Eliad Moshe
Eliad Moshe
Hi,

Can you please describe how does Dynamic Parsing work? (internally)
Does it involves changing the Spirit grammar as a pre-processing step?
It sounds really interesting.

Can you show us an example ([color=#1C2837][size=2]Epoch) ?
ApochPiQ
ApochPiQ
The response ended up being fairly elaborate, so I posted it to the Epoch wiki. You can find my full writeup here.
ApochPiQ
ApochPiQ
Writing that article made me realize how much I absolutely hate the R11 compilation process.

So R12 gets a new compiler.


Again.


Yayy!


(Progress updates will go here for those interested.)
nolongerhere
nolongerhere
Ive been following Epoch since you first started making topics about your pragmatic language of the future, but there is one thing I have always disliked and wondered about...
Why do you use that syntax for declaring variables?


If I was to skim code for variable declarations to see any initial values or the type, having it contained within parentheses and with function-like syntax, would really make it hard to read. Im guessing that it is for consistency and so that there doesnt have to be a special case, as far as syntax goes, for variable declarations. I know other languages have done it this way, but it is so hard to read! I like the language a lot and its goals, but I cant seem to get used to this!

Perhaps you can shed some light on the decisions behind this and why you chose it over other possibilities?
[font="Monaco,"]
EDIT: I also find it weird how declaring an instance of a structure is done. It looks so ugly to me. If my structure has 3 members, then when I declare an instance, there will actually be 4 things within the parentheses because of the identifier.[/font]
ApochPiQ
ApochPiQ
A few reasons, actually; one of them is that the syntax gives a clean, consistent structure to everything - variables, function calls, entity invocations, and so on. Another reason is that Epoch was originally a modified Lisp syntax with prefix rules, and the existing syntax is largely a historical accident that fell out of that background. Yet another reason is that with proper syntax highlighting support it makes no difference to the eye anyways; you use the coloration to spot a variable, not the keyword or syntax. But there is one chief reason that trumps all of them, which I'll spend a few minutes digging into.

There is actually no such thing as a variable definition in Epoch.

Every "variable" is actually a call to a constructor. A constructor is a function of the following form:

TypeName : (identifier(new_variable_name)) -> (TypeName(ret, ...)) [constructor]

These are typically created in one of two ways. For user-defined types (i.e. structures in the current versions of Epoch), a constructor maps an identifier and any appropriate inputs onto a new structure instance with the desired member field values. These can be defined arbitrarily by the programmer. For built-in types, the constructor forwards to an internal constructor which is provided by the standard library implementation. The internal constructor is a function that looks like this:

internal_constructor<type(new_variable_type)> :
(identifier(new_variable_name), new_variable_type(value))
->
(new_variable_type(value)) [constructor]


(Note that this hints at some generic programming functionality which is not yet present in released Epoch implementations. The actual function internal_constructor is implemented in C++ in current releases.)

So you can think of an integer variable declaration as a call to this function:

integer :
(identifier(new_variable_name), integer(value))
->
(internal_constructor(ret, integer, value)) [constructor]


The question, then, is why does calling this function create a local variable?

To answer this, we turn to the idea of function tags. Function tags are keywords or parameterized sequences in square brackets which sit between the function signature and its code body (if it has one). One built-in function tag is "external" which allows us to call into C DLLs; another is "constructor." The constructor tag is very powerful because it tells the compiler that the function has some associated logic which must be performed at compile time. (Note: in the future there will be other tags which allow for compile-time execution of entire Epoch programs, allowing for Lisp-style macro processing.)

When the compiler encounters a call to a function which is tagged as a constructor, it invokes a tag rule which is defined by the standard library implementation. This rule looks at the first parameter to the function, which should be of type "identifier", and grabs the value which was passed to the function call. This value is then used to add a local variable to the stack frame of the current scope. Henceforth, code being compiled within that scope has access to the variable, via the name passed to the constructor.

This is not just theoretical or philosophical hand-waving, mind you; this is actually how the language works in the implementation. Moreover, this is the exact setup that allows for custom constructor overloads and implicit conversion operators.


Consider the following function:

integer : (identifier(new), string(value)) -> (integer(ret, cast(integer, value))) [constructor]

If we were to place this function in an Epoch program, it would be possible to write the following code:

integer(foo, "42")

Poof! Epoch is now dynamically typed wink.gif

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.