I love these little embedded languages, but I really want one that supports type annotations. I'm specifically want something that can generate large configuration structures (e.g., Kubernetes YAMLs, CloudFormation YAMLs, Terraform HCLs, etc) and I want some static typing because the iteration loops can be quite long (minutes) which is too long to test every branch and writing and maintaining unit tests to catch type errors is a huge waste of time.
It's close. I also want something that is going to be syntactically familiar to humble programmers (my target users, myself, etc); Dhall's syntax is clearly Haskell-adjacent which is an obstacle for many of us. I'll probably just roll my own using a Rust-like syntax.
If you don't mind, what is it about the syntax of Dhall that you find hard?
In my experience, Haskell's syntax is much simpler than the syntax of any of the popular languages. The "hard" parts of Haskell come with all the abstractions people make (esp. how it encodes side effects), the laziness, and the language extensions. Dhall has none of those.
It seems to be a very polarizing topic. People who like Haskell's syntax find it very readable, others don't. In my estimation, there are a lot more in the "others" category than in the Haskell category. Whether my target audience has trouble with Haskell syntax because of some inherent property of the syntax or merely because their background isn't in Haskell doesn't ultimately matter--the requirement is that my users can quickly be productive (minimal learning curve, etc).
To be very explicit, I'm dodging the question because it's ultimately subjective and people come out of the woodwork with the same predictable talking points ("syntax doesn't matter", "but it's VERY readable! look how few characters!") and I don't have the energy this morning.
I think you misread, but maybe I should've worded it more clearly: the silly people are those hypothetical people who say that "syntax doesn't matter" and "fewer characters means better syntax".
I frankly think that alternative syntaxes for the same languages ought to be a thing. Case in point: OCaml and Reason.
I can imagine a curly-braced syntax for Dhall (BTW Haskell does have curly-braced syntax when you need it), curly-braced syntax for Python, etc. Indent-based syntax for HTML was implemented (but I forgot the name).
Also, RTL syntax and Arabic / Hebrew keywords for languages have also been implemented; this must be a huge help to those whose native writhing systems are RTL. Try writing your code right-indented and with all expressions backwards.
The syntax seems generally fine (Python's type annotation syntax is shoe-horned to minimize changes to the parser--if you're allowing yourself to rewrite the parser, why not improve the type annotations? Indeed, why not stick to a more Rust-like syntax in general?). I would want the ability to restrict the standard library and builtins (no I/O) a la Lua. Also, if the type system involves a borrow checker, I'll pass (borrow checking is cool but it doesn't make sense in what is ultimately intended to be a configuration language).
I'm open to having another syntax, another parser that produces the same AST. Let the market decide. No borrow checker concepts in the source, but transpiled rust code of course uses the borrow checker.
The python standard library is where I think the strength of this approach lies. Was reading some history on why reddit was rewritten in python (after initially implemented in LISP).
Transpiling python stdlib is a significant task in itself and could use more love.
Having a stdlib is fine, but it’s incompatible with an embedded scripting language where we want to provide and control the APIs that are available to the user. It sounds like you’re building a general purpose application programming language which is fine, but the tradeoffs differ.
I've tried Cue, it gives you a type system for static JSON, but the support for functions seems either absent or very, very convoluted. I want to expose this configuration language as an interface to my tools, so the configuration language needs to be somewhat familiar (no need to reinvent functions poorly).
I guess no one mentioned jsonnet yet, but it's probably the most popular thing in use (along with ksonnet). But really, it's the most awful of the lot - where you have to program using a strange bastardization of JSON (Hello XML nightmares of the 90s/00s). Try using that for a while, and you'll be begging to use dhall-lang.
I love all new pl projects posted to hn, it’s the main reason I come here. This one seems cool and I love the logo especially.
My suggestion to the author is to think more on the selling points of the lang. is it the intention of this language is remain small and fast, or is it just a temporary property of the language because it is so new? Most languages start out as fast as C on toy problems, and then slow down as programs become larger as the language becomes more capable. Often it’s the case then you implement the cool features you want your language to have, it’s several orders of magnitude slower than when you started. That’s why so much time is spent on compiler optimizations.
I have this desire to create a (maybe domain-specific / not Turing) programming language. I have some use cases involving constraint satisfaction but basically it's a rock'n roll dream kind of thing for me.
> The language is written using Wren Language and their wonderful book Crafting Interpreters as a reference.
I had to read this a few times to understand that this isn’t saying that Pocketlang is not written in Wren. I was looking for Wren code in the repo and I was slightly confused.
Very cool project though! I always love to see projects inspired by Crafting Interpreters.
I was about to post similar because in the git repo it also states:
> Pocketlang is a small (~3000 semicolons) and fast functional language written in C
From what I can tell from the comments, pocketlang re-uses and/or re-implements some functionality from the wren language source code:
$ grep wren src/*.h src/*.c
src/pk_compiler.c: /* String interpolation (reference wren-lang)
src/pk_utils.h:// Source : https://github.com/wren-lang/wren/blob/main/src/vm/wren_utils.h#L119
src/pk_utils.h:// Copied from wren-lang.
src/pk_var.h: * wren (https://wren.io/) an awesome language written by Bob Nystrom the
src/pk_var.h: * https://github.com/wren-lang/wren/blob/main/src/vm/wren_value.h
src/pk_var.h:// usage and it has 2 formated characters (just like wren does).
As do I! Crafting interpreters is definitely one of the coolest programming books I've ever read. I used it as a blueprint for busing more than one parser in my time :D
Like regular Lua, Pocketlang uses a bytecode interpreter. AFAIK LuaJIT is still the only small language implementation that provides a JIT compiler and not just an interpreter.
I don’t know why that is - maybe there just isn’t much of a niche for a lightweight JIT compiler.
Probably because a JIT conflicts with the goal of most embedded languages to be small and simple. LuaJIT does manage to be relatively small, but it (meaning the implementation) certainly isn't simple.
That's always a fun subject for a late night argument at the pub.
When I was in school (1990s), simply having first-class procedures was good enough for most people to consider a language functional. I'd assume that's roughly the definition being used here.
But you go back to 20 years before that, and it wasn't totally unheard of to see higher-order functions in procedural languages (such as Algol 68) and object-oriented languages (such as Smalltalk). And you fast forward 20 years, and about the only language without some form of first-class procedures that's still allowed to exist seems to be C. And in the middle there's always been Python.
You could go all purist and say that, since John Backus coined the term, we should stick with his definition. In that case lisps are definitely out (he explicitly said so in his paper), and it's possible that the only genuine examples are Miranda, Haskell and friends, because even allowing mutation or an imperative style will disqualify you. But I'm not sure everyone really wants to snub Robin Milner like that.
It's almost like it's an ill-defined term with fuzzy boundaries that means whatever the person currently using it wants to tell you they meant by it, and trying to be pedantic about it is mostly just a good way to have a frustrating conversation. Kind of like just about every adjective that can be used to describe programming languages.
I thought Miranda/Haskell were much more in the vein of Lisp/Lambda Calculus than Backus's ideas, which were more radical and not widely adopted to this day [1]. I always thought of Haskell as simply what Lisp would be if it had implemented a typed lambda calculus rather than an untyped one.
This looks nice! Is there a sample how to implement your own module in C and bind that to the scripting language?
[Edit]: To answer myself the API seems really clean and neat, it looks like the patterns used to implement the standard modules can be used as is - e.g. to implement a third party native module, a good pattern to start from is to observe how _fileOpen is implemented in cli/modules.c and added to module using pkModuleAddFunction
Cute but pkInterpretSource() doesn't have any throttling, like the amount of instructions/nodes to execute before suspending, this would be very useful in games. You could limit NPC's ai to 50 instructions or so so that it doesn't cause rendering lag and the ai script would be run in multiple frames until it finishes.
> You could limit NPC's ai to 50 instructions or so
The problem with this approach is that limiting the number of instructions doesn’t necessarily limit the amount of time taken. Any single VM instruction could be a call into a C function that performs a long-running computation.
If you want to robustly limit the amount of time a script runs for, you have to actually time it.
I guess the fiber/coroutine support would be a better solution to this problem? If this works like I think it should work, the code can decide itself when it is a good time to yield control back to the environment instead of freezing execution in a random location until the next frame.
For instance when evaluating NPC AIs it may be important that an NPC isn't left dangling in a random state at the end of a frame, but either has finished evaluating, or hasn't started yet.
Code cannot decide when to yield because it doesn't know how many NPCs I am running in one frame. Maybe I need 10, maybe 2 longer and 8 just quickly for long running/low complexity jobs. If the limit is reached in the middle of "if" statement, e.g.
if x < sin(5) + cos(20) then y = 5;
If "x" and "sin(5)" was evaluated but not "cos(20)" then it should just remember VM state and continue next time. I did this in JS and it worked great for distributing NPC load between frames (https://twitter.com/dusan_halicky/status/1173924435923001344) and it didn't required any understanding of the code (I didn't need to carefully check when I need to stop execution), just VM snapshot and continue next frame.
Since it’s a small language that already supports yielding for I/O, it shouldn’t be too hard to automatically insert an instruction into all loops to check if they need to pause. You could write it manually to prototype.
(Or at least I assume it works that way based on studying Wren.)
Off topic: for those thinking it was a programming language optimized for a phone (like I did) the only one I’ve seen come close to being usable is J (APL derivative). I’ve been able to solve quite a few advent of code challenges this way:
Isn't python style more about its built-in comprehensions and much less about generic functions?
[fn x for x in ... if ...]
I know there's a `map` in, say, `multiprocessing.Pool`, but using that doesn't feel as natural as a comprehension in python or `map` in, say, Haskell or Kotlin.