Hacker News new | past | comments | ask | show | jobs | submit login
Wren is a small, fast, class-based concurrent scripting language (wren.io)
122 points by jay_kyburz 5 days ago | hide | past | web | favorite | 54 comments

Wren seems like a really clean alternative to a language like Python, but it makes one mistake that really drives me nuts. In order to provide binary operators, you must implement a method on the left object. From the documentation [0]:

> The left operand is the receiver, and the right operand gets passed to it. So a + b is semantically interpreted as “invoke the +(_) method on a, passing it b“.

This means that if you want to implement complex numbers, or matrices, or a bunch of other useful stuff, you'd have to be able to extend the type on the left (which you didn't write). You really want to be able to do things like:

    var z = Complex.new(1.0, 2.0)
    var result = 3.0 - z

    var A = Matrix.ident(3, 3)
    var scaled = 4.0*A
Python hacks around this by having "right' versions of these methods too. It's kind of ugly - if the left object fails to support the operator method (__sub__ or __mul__) with the right type, the interpreter looks for a __rsub__ or __rmul__ operator on the right object to try. Ugly, but it works.

C++ works around this a much better way: You can either have the operator method on the left object, or have an overloaded operator function that isn't tied to either object.

Rust puts the operators on the left object too, but in some cases you can add methods/trait specializations to left classes. Unfortunately, Rust doesn't let you do this with generics, but that's a longer more complicated topic.

Maybe there's some way Wren lets you tack on additional methods to the builtin Num class (monkey patching), but I couldn't find it. I would love an elegant alternative to Python, but this is enough of an issue that it keeps me from using Wren.

[0] - https://wren.io/method-calls.html#operators

The solution I've been playing with for $interp_thing (mostly in my head so far) is roughly the python version but with some type dispatch checking to pick the "most specific" of the two - which is easy when one is a subclass of the other but gets gribbly in more complicated cases. I suspect ultimately it'll be a case of "run a multimethod style selection algorithm for 'most specific' and arbitrarily declare 'LHS wins'" but I've not got far enough to consider that remotely fully baked.

($interp_thing being a placeholder for something I'm experimenting with of late)

Yes, OO inheritance and subclassing complicates the multimethod selection algorithm, allowing for there to be ambiguity or ties. I don't use inheritance very often, so if it was my $interp_thing I would just leave it out.

I don't remember the details very well, but "The Art of the Metaobject Protocol" is a book which goes into this topic. That's in the context of multimethods for Common Lisp, and I suspect they were very thorough and careful with the solution they used. https://en.wikipedia.org/wiki/The_Art_of_the_Metaobject_Prot...

You also have the Ruby solution of calling a `coerce` [0] method on the right hand side.

[0] - https://github.com/ruby/ruby/blob/0703e014713ae92f4c8a2b31e3...

I don't know Ruby very well, and I'm curious how this would work with my example above. It seems like Ruby's builtin number types, upon not knowing what to do, calls coerce on the right-side object. That right-side object returns a pair of new objects, and then the binary operation is called as a method on the left one of those?

If I've got that right, it sounds functionally similar to the Python way. It's a little awkward if you want different types depending on the operation. For instance, maybe A-B returns a different type than A/B. In both subtraction and division, it looks like Ruby would call coerce(), and coerce doesn't know the operator. Still that could build some smart placeholder type that then knows how to treat the operations differently...

For my example with Complex and Matrix types, it seems like the Complex type would need to implement the coerce protocol on its own. Maybe I've got that wrong, but does the coerce thing automatically happen for any type, or is that special behavior implemented by Ruby's Number types?

> If I've got that right

You did.

> it sounds functionally similar to the Python way

It definitely is, however it allows to handle all operators by defining a single method, so it's a bit more usable.

> does the coerce thing automatically happen for any type, or is that special behavior implemented by Ruby's Number types?

Only for number types. For other core types using binary operators, you have dedicated implicit conversion methods. For instance `"foo" + MyType.new` will try to call `MyType.new.to_str`.

`to_str` being specifically for implicit conversions, and `to_s` for explicit conversions. So implicit conversions won't happen unless specifically defined.

This approach seems nice in that it fast-tracks the common case and only delegates if needed.

> it allows to handle all operators by defining a single method,

That's nice and pragmatic too.

Back to the subject of this submission, I suspect the coerce idea could be bolted onto Wren without really breaking or changing much of anything (just an additional branch in the error handling of Wren's builtin Num type). That would allow Wren to grow it's own numpy-like library someday.

Ruby also let's you extend all object/classes - all the way up/down to Object.

I don't know that I think that's a very good idea, but at least it's possible.

It allows things like:

  require 'active_support/time'

Lua addresses this problem by having metamethods, with a special way of failing over when operators meet a type error:


>If any operand for an addition is not a number (nor a string coercible to a number), Lua will try to call a metamethod. First, Lua will check the first operand (even if it is valid). If that operand does not define a metamethod for __add, then Lua will check the second operand. If Lua can find a metamethod, it calls the metamethod with the two operands as arguments, and the result of the call (adjusted to one value) is the result of the operation. Otherwise, it raises an error.

Thank you for the link, but I'm not sure this solves the problem. I mean, what if Abbie makes complex numbers, so she provides a metamethod to work with the builtin number type. Then Bobby wants to build on Abbie's work and add matrices. Lets say Abbie's complex type is on the left, but her metamethod doesn't know anything about matrices... Bobby is willing to support complex numbers multiplied with matrices, but the matrix metamethod never gets called:

    z = complex(1, 2)
    A = ident(3, 3)
    -- Won't this next line call the wrong metamethod?
    X = z*A
For a dynamicly typed language, I think you'd want something like multimethods to do this cleanly. (Or something like Python's dirtier approach)

If you want to allow unknown types to do your multiplying, you can write __mul like so:

    elseif getmetatable(right).__mul and getmetatable(right) ~= complex then
       return getmetatable(right).__mul(left, right)
Of course this requires a little forward thinking, but it doesn't require you to know what other metatables (classes) you'll be compatible with. It's not as clean as multiple dispatch, though.

I think the most elegant solution is to break the subordination of methods to classes by having structs (possibly with inheritance) and multimethods.

But this solution needs to write down the type(struct) of every parameter. I think the biggest reason why dynamic language is popular is they don't force users to write type every time. So this is contradicting to the charm of dynamic language(as literally, all scripting language is dynamic, including wren).

We can mark those function which doesn't specify all the types as default, but it will make code very ambiguous, like:

function add(A a, B b) function add(A a, b) function add(a, B b) function add(a, b)

You only need to specify the type if you want to dispatch on the type. In CL, unannotated arguments are considered to be of type T, which indicates they could have any value.

This isn’t any different from the way methods are dispatched in Wren or Python or anything: it’s just that the type of the single argument that’s relevant for dispatch (this/self/etc.) is the class. But, the disadvantage is that since methods are nested in the class definition, there’s no clean way to extend pre-existing classes without modifying the source of the defining class. If “method” is its own concept, on equal footing with classes, you can put all the base cases together and then users that want to extend the set of types the operation works on can specify additional cases to define the interaction between their new types and the pre-existing ones.

I’ve written a lot of Common Lisp now, and I’ve found that multimethods help eliminate a lot of boilerplate where you write classes that wrap an existing class and delegate to it and similar patterns.


    class complex(real, imag);
    function add(number a, complex b) {}
    function add(complex a, complex b) {}

Why could you not write:

z + -3.0 and A * 4.0 ?

Yeah, you could do that in some cases. You could also just call functions and not have operator overloading at all.

However, it's not hard to construct a case where instead of using literal values they are arguments to a function (or elements in a list), and you don't know which type comes first until runtime.

But really, if you're translating math from an equation (say in a book or something), juggling the order of the binary operators is gross.

One thing I love about javascript is the ability to quickly and easily assign/remove whatever fields I'd like:

    let a = {};
    a.somename = "some val";
    a.newname = "some val";
    a["a computed name"] =  "some other val";
    delete a.somename;
    if (a.neverseenthisbefore) {
      log("just doesn't log!");
Python dictionaries requiring the ["fieldname"] syntax or .get to avoid KeyErrors on undefined keys has always pained me when whipping something short together. Similarly, here in Wren it seems like Maps require substantial and strict syntax, and Classes are not meant to allow definition of arbitrary Fields at runtime.

That one small thing is a key piece of flexibility I want when experimenting. I shouldn't need to write a getter and a setter down in a distant file on the correct place in a class hierarchy just because I chose to latch a bit of temporary state onto an object while I do a bit of work.

In serious code for serious business, that kind of freedom and nonchalance can be quite dangerous. But when I'm having fun and just trying something, which is the only context where I'm really trying new languages out for the moment, the guard rails chafe!

(still, this looks like a pretty neat little language)

Side note: You can use python classes for that. It's a common pattern for me to have something like this in my projects

    class Holder:
Simply add any attributes at runtime. It's essentially a different interface for the built-in dict of the instances. Add a bit of getattr magic and you can read undefined attributes, too.

> The VM implementation is under 4,000 semicolons.

> Lightweight fibers are core to the execution model and let you organize your program into a flock of communicating coroutines.

> Wren is intended for embedding in applications. It has no dependencies, a small standard library, and an easy-to-use C API.

Wow, this would be ideal for me to write loadtests in! In broad strokes, I just want to spin up N threads making request after request, in an event loop with basic tracking. Is there a JSON or Network library available yet?

Would like to start using Wren though issues like this put me off: "Stack corruption" https://github.com/wren-lang/wren/issues/761

Using older and more established languages seems less risky.

I believe this language was originally the creation of the guy who wrote the crafting interpreters book, but it definitely looks like it has grown well beyond his influence.

From what I understand, this language was indeed developed by Bob Nystrom. AFAICT, He's passed the torch to the team behind the Luxe game engine, which uses wren as an embedded scripting language for game programming.

Looks like what JavaScript would be like without all the baggage from the ‘90’s.

I've an issue on Wren's FFI: binding foreign methods is slow. https://github.com/wren-lang/wren/issues/648

The name wren is already used by: https://projectwren.com/

The Wren language has been around a lot longer than the project you linked.

The guy behing Wren is Bob Nystrom, member of Dart language team and author of Crafting Interpreters book.

I'd be interested in if this is comparable to Gleam, a BEAM-based scripting language, that I discovered through HN.

I believe gleam is a compiled language, and no more of a scripting language than say erlang (with escript) or elixir (via .exs files).

I see.

I also realized after I posted that Wren is apparently meant to be embedded.

Looks nice. It reminds very much of C and C++ programming. What are some of its use cases?

If you're writing an application in C or C++ and want to embed a scripting language, and you think Lua is kinda weird and would prefer a more straightforwardly object-oriented language, then Wren may be worth trying.

But I think there is only one active maintainer so you should be prepared to write your own patches if you need to fix something.

I am building tool for generating PDFs (https://docula.app) and I am using Wren to allow user scripts: things like show/hide label on some value or maybe display number in red when the value is > 0 and green otherwise. I was thinking about using Lua, but Wren seems more approachable to me.

I like your product. Do you find Wren reliable when implementing features, like did it ever broke?

Well, it seems to be working very well, but I am still in development mode with one customer and very little scripting going on. I will know more after general public is going to use it and will try to use things I haven't think about.

I am not that concerned of Wren failing as a language, but I looking for a ways to prevent users write code that doesn't work without having to know what is going on, which is something Lua is excellent in (typo in variable name in if statement? Valid code for sure)

I think it got it's start as part of https://luxeengine.com/ which transitioned away from Haxe

Wren was originally written by Bob Nystrom, author of Crafting Interpreters. He recently handed it off to another maintainer.

Looks like ruby with {}. Performs like ruby

> Wren is fast. A fast single-pass compiler to tight bytecode

Bit of equivocation here.

Wren is dynamically typed. It will never be fast.

At best, it will probably be one tenth as fast as the slowest statically typed language.

Maybe it's fast to compile, but it will never run fast.

> Wren is a scripting language.

As far as I know "scripting language" doesn't have a widely-agreed upon definition and we should all stop using it.

If anyone were to ask me, I'd say that a "script" is a program that contains a sequence of commands. It executes transiently for some side effect, as opposed to creating a persistent process responding to events.

So for example, bash is clearly a scripting language, and if one is being rude about the appropriate uses for perl, then perl would be too. Python can be use for "scripting" but certainly isn't in general a scripting language by this definition. And I don't really think "scripting language" is a helpful term for Javascript, since it's most important role by far is in as an event-driven persistent process. However, some people (especially old-school C-programmer types) use "scripting language" to mean an interpreted language, I think.

I think for Wren, maybe it's referring to a usage of "scripting" that C programmers associate with Lua?? None of the things it mentions are things that I personally associate with the word "scripting":

> Wren is intended for embedding in applications. It has no dependencies, a small standard library, and an easy-to-use C API. It compiles cleanly as C99, C++98 or anything later.

Or is the idea that both Lua (and thus Wren) and Javascript are "scripting" languages in the sense that they augment an executable written in a "proper" language with additional run-time functionality? If so, then that's interesting but I don't think many people get it (it only occurred to me while writing this) and we should clarify the terminology.

I understand about avoiding unclear terms, but I think "scripting language" actually does serve a useful purpose as an umbrella term for all those kinds of languages. It's not technically precise, but if you describe a language as "a scripting language", it does clear away a large part of the design space (not Haskell, C, Forth, or J) and tells you that this is meant to be user-friendly as opposed to performant, terse, high-assurance, etc. That's worth something. Of course if someone is still interested, then you want to get more precise pretty quickly. I think Wren does well enough at that on its landing page.

> user-friendly as opposed to performant, terse, high-assurance, etc.

Strange, I really don't see that as an axis worth recognizing with that, or any, terminology (but whenever I bring this up I get voted down, so I guess more people agree with you.)

I think the core of my disagreement is that, just because a language is user-friendly, that does not mean that it is not a "professional" or "high-assurance" language. I'm not saying that you are using it in this fashion, but I believe that with the terminological strategy you are suggesting, the term becomes a pejorative, wielded by those who use (C/C++/Rust/Java/Haskell/etc) as a subtle put-down to people who are using more user-friendly languages.

Related, I think it's a rather confusing axis that doesn't fit the space of programming languages well:

Go is user-friendly (I gather) and verbose, but performant and high-assurance.

Python is user-friendly and terse, but non-performant and low-assurance (I'm a python programmer, but the run-time bugs put it in that category if we're comparing to statically typed languages). A lot of website HTTP handlers are written in Python, so "scripting" language seems like a very poor description for what it's doing there.

IMO reasonable axes that are worth recognizing with terminology include interpreted vs compiled, statically vs dynamically typed, weakly vs strongly typed, but do not include {user-friendly} vs {performant, terse, high-assurance}.

It might make more sense if you think of "scripting language" as being a statement of intent than a technical classification. I have seen it used as a perjorative, which I agree is stupid.

More generally, performance, ease of programming, correctness, etc are goals, whereas typing systems and compilation strategies are techniques that can be used to reach those goals. "Axes" is probably overthinking it, as none of these criteria or properties are linear, but terms for all of them can be worthwhile if used to answer the right questions.

Since goals have a strong influence on technical properties, it's not completely crazy to summarize a language's technical features in terms of its goals. "Scripting language" makes sense as a description for languages that emphasize ease of programming (with overtones of interacting with a larger system) over other features. You also have a clue about a language called "high performance": probably not interpreted or dynamically typed (although exceptions include kdb, arguably Java and Julia).

Finally, not every term needs a rigorous definition in terms of more primitive terms. Sometimes you just need a name for a category that everyone kind of knows is there. This is probably the more realistic origin of the term, though I do think the underlying reason for that category existing is mostly the goal-driven one described in my previous paragraph.

Note: In the bit you quoted, I certainly didn't mean to say that "user friendly" was necessarily opposed to all those other goal properties. I was just trying to give examples of other things you might want to prioritize in a design.

I think the best colloquial definition at this point is "a higher-level language that delegates to lower-level code for core logic". Usually that higher-level language is interpreted and dynamic, but it doesn't have to be. I read a post once that suggested using Haskell as a "scripting language" for Rust.

This definition is most precisely used in games, where the dichotomy is particularly common due to their high requirements for both performance and creative expressivity. But it also nicely encompasses bash scripts delegating to C utilities, JavaScript delegating to the web browser, and Python delegating to NumPy. It's also generally the goal with embedded languages like the one in the OP.

Thanks, yes that's very helpful. In my experience (scientific and web backend programming, no games and no industry compiled) there's a large number of people who are not aware of this definition of scripting. For them, a "script" is a program written in a procedural style that runs and does some stuff and exits.

So, for example, web backend python programmers might "write a script to run on prod to fix the messed up data", but they would not refer to their flask/django HTTP handler code as scripts (whether they defer to numpy or anything else).

In research labs, scripts (sensu me) are often the only category of code that is written. They fit the definition you gave in that they probably delegate to things, and the one I gave in that they are typically procedural and linear.

My definition for scripting language is even more basic than the other commenters here - a scripting language is one where the user executes the source file (the script).

Of course the system is free to do whatever it wants under the hood (e.g. Python generates .pyc files) etc, but as a user if you can double click the main source file/feed it to a program and have the full program run, then it's a scripting language.

I think of it, very generally, as: scripting language = writing a 10 line program isn't a chore; non-scripting language: writing a 1000 line program isn't a chore

OK, so according to that criterion, where would you classify





Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact