Wren is a small, fast, class-based concurrent scripting language
wren.io
wren.io
> 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.
https://www.lua.org/manual/5.3/manual.html#2.4
>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.
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) [...]
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.[0] - https://github.com/ruby/ruby/blob/0703e014713ae92f4c8a2b31e3...
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'
5.days.agoIf 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?
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.
> 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.
class complex(real, imag);
function add(number a, complex b) {}
function add(complex a, complex b) {}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)
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.
z + -3.0 and A * 4.0 ?
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.
($interp_thing being a placeholder for something I'm experimenting with of late)
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...
2018 https://news.ycombinator.com/item?id=16945227
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)
class Holder:
pass
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.> 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.
Using older and more established languages seems less risky.
I also realized after I posted that Wren is apparently meant to be embedded.
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)
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.
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.
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.
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}.
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.
Python
Ruby
Javascript
?
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.
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.
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.