Forth implemented in Rust trait system
github.com
github.com
You can execute an ordinary functions at compile-time to read a DSL from a string or read attributes (reflective metaprogramming) on your program's classes. Take the string it outputs, use mixin(), and you have code. For example:
// Sort a constant declaration at Compile-Time
enum a = [ 3, 1, 2, 4, 0 ];
static immutable b = sort(a);
"a" only appears in the compiler's memory. "sort" is a normal function that runs at compile-time. "allowing accessto the full language at compile-time" is similar to what dynamic languages such as Python and JavaScript give you, except D is a static language with GCC and LLVM backends.Fortraith cleverly re-purposes existing features, doing some violence to language usage conventions to achieve it.
Can you please explain what you mean by this?
However, at macro-expansion time, often what you want to do is examine a Type and expand the macro differently based on some information about the Type.
So you would like your Macro language to have some notion of manipulating Types that is absent from the language itself
Proper reflective metaprogramming would be a fairly big step though - right now, the macro systems happen well before the type system even gets a chance to look at the code, so the data to play with types in an interesting way isn't there at the right step.
Uh oh. I'd hoped the language would settle down.
The Go crowd knows when to stop. Go is mediocre, but stable.
Go got some things right, like "goroutines". Other languages are trying to add threads to a language with "async" type callbacks (Javascript), or "async" type callbacks to a language with threads (Python). Those concepts do not play well together, especially when retrofitted. Go does have a one-size-fits-most solution which handles the main use case - a server process handling a very large number of intermittently sending clients.
The same can be said of "functional" add-ons. Full-on functional languages can be OK, but functional features in an imperative language tend to be on the painful side syntactically. Retrofitting "functional" is even worse.
Because "full use of the programming language" implies Turing completeness, which means compilation may require unbounded time and compute resources. You can allow use of a non-Turing complete subset, and this is something that dependently-typed languages can do quite elegantly.
It’s a valid point. Of compilation already is Turing complete, why not just drop the pretense and allow arbitrary compile time expressions.
When you need to run an unbounded program each time you want to provide real time feedback, like type inference or in Rust case lifetime inference, you make the language tooling much less simple and accessible.
How so?
> When you need to run an unbounded program
How is a program that provably terminates but takes 2 years to finish any better for compile-time computation? You want timeouts in either case.
I'm not sure how realistic that actually is. Besides certain party tricks like encoding the ackermann functino using primitive recursion, I haven't actually seen anything like that. From my experience the vast majority of programs that don't finish in a reasonable amount of time are those that have some logic bug.
A timeout seems pretty off-putting. The idea that compilation could fail on a weaker machine just because it isn't fast enough just doesn't sit right with me.
In any case, the compile time evaluation would obviously still have a bunch of interesting constraints— like, probably reasonable to read in the contents of a file, but is it reasonable to make a network call? What about reading a file that's on a network share? Can you even tell the difference? That operation could also hang for reasons entirely outside of algorithmic complexity.
And of course, like a stack overflow, that's also true today; you can hang your compile by including a header from a network share that hangs.
So would it really be that bad to have the compilation hang, and just show a stack trace for the invoked-at-compile-time code when interrupted?
I don't think the halting problem is a deal breaker here.
Try doing anything pretty fancy at compile time with templates in C++ or macros in Rust and you'll quickly need to set the recursion depth limit higher.
The timeout should be set based on what the user of the machine considers acceptable. Why should I have to tolerate a 2 hour wait time for a compile-time computation to finish in order for the auto-complete menu to appear on emacs just because I am using a laptop from 2010?
> Besides certain party tricks like encoding the ackermann functino using primitive recursion, I haven't actually seen anything like that
Try prolog then (or something similar) and write something of the form sha512(X) = 0 this will initiate a brute-force search that will last essentially forever.
I don't understand. If you aren't willing to wait that amount of time, you're simply not getting working auto-complete at all. Hell, if you can't even compile the project, I don't see how you can meaningfully work on it all.
>Try prolog then (or something similar) and write something of the form sha512(X) = 0 this will initiate a brute-force search that will last essentially forever.
This is literally just another nonsensical party trick. I'm talking about something that someone might actually want to use, not contrived examples that really don't matter at all. I don't care to consider imaginary people that intentionally brick their programs.
> If you aren't willing to wait that amount of time, you're simply not getting working auto-complete at all
Or I am getting a working auto-complete for code that does not make the completion system to get killed by the timeout.
> Hell, if you can't even compile the project
I never mentioned not being able to compile the project.
You would want as much as possible to make the timeout not based on runtime -- but on an abstract model of the computation which doesn't actually depend on any machine particular. "Hey your program didn't compile on my machine -- try a faster cpu" would be a nightmarish interaction.
You want constraints about how long these programs are allowed to execute to be tight enough for developers to learn a sense for what is reasonable to do with these features -- as opposed to waiting for runtime.
All other tooling faces the same issue. What variables have a given type? If determining the type takes arbitrarily long, how are you supposed to discover them? Now consider the plight of an automated refactoring tool.
The IDEs and tools can still be written, of course. But they take more work and can't be as reliable.
A language that is not Turing complete at compile-time does not necessarily mean that an IDE will be able to reliably tell whether a certain piece of code has a syntax error without running a program that can take an arbitrarily amount of time to finish.
The various tools built around Java stand as an example.
In addition C++ has constexpr which marks an expression to be evaluated at compile-time.
For interpreted languages, there are no excuses; hooking into the interpreter at compile time is trivial. Full macros [0] are nice, but depends a lot on the syntax. A way to evaluate expressions at compile time [1] would go a long way.
If the result of my `sort ` function is dependent on `sizeof(void )` being 8, when I compile from my x86 hardware for a 32-bit only architecture, assumptions go awry.
Note that this isn't likely with something like sort, but definitely is* likely with precomputing values/structs etc.
You have procedural macros, which are literally "Rust code is the input, which you write rust code to manipulate, and output more rust code".
You have const fns, which are interpreted at compile time, and are closer to what you're referring to.
fn immutable foo() long:
asm(long x:"rax") "mov rax 7"
return x
Now try cross-compiling that from a 32-bit ARM machine.Aside: D is kind of weird in this regard because most of it is designed to work as a interpreted language as well as a compiled one. To the extent the D is good, it's not compiled[0]; to extent that it's compiled[0], it's not good.
0: in the language design sense, not the language implementation sense.
If you only expose a subset, you very specifically don't have the full use of the programming language.
I really don't want Rust to be crippled with a 1000 of DSL just because devs could write them easily.
When the capabilities of the constant evaluation system get improved and stabilized, we are going to see the exact feature you are describing. (With the caveat that non-deterministic functions are not allowed to ensure deterministic builds.)
// create a word
forth!(: inc 1 + ;);
// ask user for $input
type Stack = forth!($input inc return);
// if you keep the stack around it can be used again
forth!({ Stack } inc .);
// should print $input + 2For example, in CASE ... OF ... ENDOF ... ENDCASE, the input value is consumed when OF is entered. But this means that the default result, positioned before ENDCASE, has to move the input value to the top of stack so that it can be consumed before the result. The examples in the ANS standard prefer using >R ... R> as a scratchpad for this purpose, so that any number of results may be pushed into the data stack, while the input is moved onto the result stack and then pushed back to the data stack. But the whole existence of the return stack and words that use it is a curious detail that doesn't come up if you start from an RPN calculator instead of a complete Forth system. Someone writing a RPN system in an applicative language, upon seeing this semantic, might be tempted to beef up the syntax rather than add this idiom. And in Forth this idea likely was only arrived at through the numerous iterations Moore made to achieve better expression in fewer words, since the return stack is useful everywhere.
C++ is actively replacing its compile-time ML with core language features, but isn't there yet. Still, it has been many years since I needed to code any of my own TMP.
has anyone tried implementing rust in rust's trait system
Turing completeness means using the syntax of your favorite programming language is "merely" a matter of writing the appropriate program.
What GP is proposing is completely impractical, of course. But not unreasonable, and certainly not impossible.
That doesn't mean that the OP's hack can be used today, or even tomorrow for compiling-Rust-in-Rust. But you could, in theory, do so.
Encoding the inputs/outputs for a given TC system may be a pain, but that is irrelevant to expressiveness power.
In fact, in most cases, when you hear the TC claim it is about an exotic system :)
It is neat to see something more practical than an Brainf* interpreter.