2,195 karma · joined August 15, 2018
I would say that RCL [3] satisfies the criteria. It’s deterministic and pure, has metered execution, and it sandboxes filesystem access. It can read files when allowed by the sandbox policy, but in my view that makes those files part of the source code, it behaves the same as imports.
For RCL I did not want to go the “not Turing-complete” route for exactly the reason the author mentions: knowing that a program enventually terminates is not a useful property in practice. And conversely, it is possible to write very complex programs in total languages like Agda, non-Turing-completeness is no guarantee for simple programs/configurations. All loops in RCL are bounded, but it has functions, so it has recursion. It does not have tail calls, so at first I added a recursion depth limit (to prevent overflowing the native stack), but then the fuzzer discovered a function that runs in constant stack space, yet it hangs. I still don't fully understand how it works:
let f = g => g(g(h => k => g(g(h))));
f(f)
Anyway, it is not a problem in practice that this kind of pathological function can be expressed, I just put a limit on the number of execution steps (a “gas limit”, or what the author calls “metered execution”). For keeping code simple, I think the fact that the built-in looping constructs are bounded, and recursion is awkward, are a good nudge, but in the end the most valuable tool is code review and applying good judgment. [1]: https://dhall-lang.org/
[2]: https://cuelang.org/
[3]: https://rcl-lang.org/It’s hard to be sure about whether anything will always be true, but I intend to not expose environment variables, CLI arguments, or other impurities to RCL.
> What about environment variables? If the language ever needs to handle parameters, this assumption breaks and thus compile time and runtime will be separated.
This is more of an argument about the definition of “input”. In RCL you can import other RCL/json files (as structured data) or any UTF-8 file (as a string). If the evaluation of the program depended on environment variables (or if you could pass input values on the CLI), that would be very annoying for reproducibility. You’d have to specify out of band how to invoke `rcl`, and how to pass the right values. Instead, we can simply write those values to a file and import them. For my definition of “program”, by doing that, they have become part of the program, so we again have a program with no _external_ parameters.
Regardless of whether you call them inputs, the person who sets these values also immediately evaluates the program, so if the combination of parameters causes an error somewhere, the programmer can fix the input or the program. That’s different from a daemon, or program deployed to users, where the programmer does not yet know at development time which values can occur at runtime.
I can think of one case where this does not hold. What if you want to develop a _library_ of RCL code, with functions where you don’t know how they will be called? I don’t think that RCL programs should grow so complex that this is needed, but maybe I am too naive or nearsighted about that, and it will happen anyway. (Sharing schemas may be useful, but they only define types, not functions.) Even in that case, a configuration language is forgiving, because when the program is evaluated there is a programmer running it, even if it is not the person who wrote the function where the error originates.
(What if you build some system that autonomously generates json files and calls an RCL program that imports them, without a human watching? Sure you can do that, but then if it fails at runtime, I think that’s a case of the Baton Roue meme.)
I guess the summary of this is: for some languages, yes the type system should be very strict, and you want to know that when you write a function, it can’t fail at runtime. But for a configuration language, that is not a problem that the type system needs to solve. I wrote more about this in part 1: https://ruudvanasseldonk.com/2024/a-type-system-for-rcl-part....
One pitfall with non-coverage-driven randomized testing like QuickCheck, is that how good your tests are depends a lot on the generator. It may be very rarely generating interesting inputs because you biased the generator in the wrong way, and depending on how you do the generation, you need to be careful to ensure the generator halts. With coverage-driven fuzzing all of these problems go away; you don’t have to be smart to choose distributions so that interesting cases are more common, coverage instrumentation will automatically discover new paths in your program and drill down on them.
But isn’t fuzzing about feeding a large program or function random bytestrings as inputs, whereas property-based testing is about testing properties about data structures? It is true that fuzzers operate on bytestrings, but there is no rule that says we can’t use that bytestring to generate a data structure (in a sense, replacing the role of the random seed). And indeed this is what the Arbitrary crate [1] in Rust does, it gives tools and even derive macros to automatically generate values of your data types in the same way that QuickCheck can. The fuzzing community calls this Structure-Aware Fuzzing and there is a chapter about it in the Rust fuzzing book [2]. There are also tools like libprotobuf-mutator [3] that substitute fuzzers’ naive mutation strategies, but even with naive strategies fuzzers can usually get to 100% coverage with appropriate measures (e.g. recomputing checksums after mutation, if the data structure contains checksums).
I am using this extensively in my own projects. For example, RCL (a configuration language that is a superset of json) contains multiple fuzzers that test various properties [4], such as idempotency of the formatter. In the beginning it used the raw source files as inputs but I also added a more advanced generator that wastes less cycles on inputs that get rejected by the parser. The fuzzer has caught serveral bugs, and most of them would have had no hope of being found with naive randomized testing, because they required a cascade of unlikely events.
Structure-aware fuzzing is not limited to generating data structures either, you can use it to generate reified commands to test a stateful API, as you describe in the _Stateful property-based testing_ section. The Rust fuzzing book has an example of this [5], and I use this approach to fuzz a tree implementation in Noblit [6].
[1]: https://docs.rs/arbitrary/latest/arbitrary/ [2]: https://rust-fuzz.github.io/book/cargo-fuzz/structure-aware-... [3]: https://github.com/google/libprotobuf-mutator [4]: https://docs.ruuda.nl/rcl/testing/#fuzz-tests [5]: https://rust-fuzz.github.io/book/cargo-fuzz/structure-aware-... [6]: https://github.com/ruuda/noblit/blob/a0fd1342c4aa6e05f2b1c4e...
Vitalik writes about this too: https://vitalik.eth.limo/general/2021/08/22/prices.html
Vitalik writes about this too: https://vitalik.eth.limo/general/2021/08/22/prices.html
[
for book in input.store.book:
if book.price < 10:
book.title
]Chorus One operates validators on many proof-of-stake blockchains (the ones where security is based on a Byzantine fault-tolerant consensus algorithm rather than wasting energy). We are hiring for several roles, but the one I will highlight is what we call the Platforms Engineer. Some companies call this Site Reliability Engineering or Devops.
The main thing we do is take upstream software, build it, run it on our infrastructure, and then monitor it and optimize that setup. Some things that make this interesting are:
* Building automation that enables us to do this for many networks (50+ currently). * Doing this with high uptime, building automation for failover, etc. * Working with software that is on the one hand cutting-edge and doing interesting things (consensus algorithms, distributed systems, cryptography), but on the other hand that means it’s immature and often not easy to operate and monitor. Often we have to build custom tools, and dive into the source code of the project. We contribute patches upstream when it makes sense. * Some of these projects are exercising the limits of what a machine can do, we have to do some low-level investigation that requires understanding of what the Linux kernel and network hardware are doing to properly identify what’s going on.
We do have a small cloud footprint but run primarily on bare metal. We are looking for people who can not just configure services offered by the public clouds, but who deeply understand what lies below; people who could build their own cloud. (That sounds a bit pretentious and it’s not exactly what we do, but it does involve many of the same aspects.) If this sounds interesting to you, check out https://careers.chorus.one/o/platforms-engineer-remote.
I noticed the opposite, it always fails to locate any satellites, even when GPS is still turned on in aeroplane mode. I'm not sure why.