The Lily Programming Language
github.com
github.com
It's nice to see that there's a trend for such small, (relatively) simple languages to include nice features like first-class functions, convenient lambda notation, sum types, type inference, etc. A few years ago it seemed most new languages were just nitpicking over syntax, without bringing much to the table. I'm glad to see we're leaving the local minimum of "class-based OO with slight different static method mechanism"!
A pet peeve of mine is the sloppyness by which .NET allocates things like iterators etc. I'd very much like to see zero-cost abstractions in a new language, so that there is no surprise differences between various ways of iterating over collections. I read it only quickly but couldn't find anything related to how enumeration/iteration works under the hood.
Currently, the best you can get for iteration of Lists and Hashes is to do each-ing the way you'd go about it in Ruby, except that the language currently lacks iterators.
That's not to say I don't want iterators. But I've spent a huge amount of time of this release in making sure that all of the built-in classes get dynaloaded so that memory usage stays low. Now that everything's getting dynaloaded properly, I've less of a problem with doing iterators.
The biggest design mistake in Java was the omission of stack allocated value types, and the same for C# was the distinction between value and object types. If something can be done on the stack it should be done on the stack - the biggest lie we were told was that "short lived objects are cheap".
(n.b.: when I write C#, it's for game development, and I am constantly thinking about structs versus classes and I'm very careful about them, so my perspective is probably not the same as your average C# developer.)
Or maybe I'm in a total echo chamber. Working with a client today, they were astonished to find out that:
var x = new List() { 1 , 2 };
var y = x;
x.Clear(); // This affects y.
Absolutely astounded. They also had no concept of local variable scope, thinking they had to manually clear out the List so the next invocation of the function wouldn't have the old data. They aren't fresh devs. They've had professional programming jobs. They've built a successful company based on their code. It does non-trivial work (not just a web UI on a DB) and processes many $/sec. In fact, I'm impressed how successful they are.So who knows, maybe MS's overwhelming feedback is from people that fundamentally don't understand what's going on, and they're the ones with money?
To be fair, I've seen successful C projects with lines like: buffer = malloc(....); // gets memory
I never got around to writing in detail about it, but I have that goal with iteration in Wren, and came up with a little iteration protocol[1] that I think accomplishes it.
In most languages a loop structure like:
for (i in sequence) {
print(i);
}
Desugars to something like: {
var __iterator = sequence.getIterator()
while (__iterator.moveNext()) {
var i = __iterator.getValue()
print(i)
}
}
You've got one fundamental piece of state: a cursor that tracks where you currently are in the sequence. This is separate from the sequence itself because you can iterate over the same sequence multiple times, sometimes even simultaneously.And you have three fundamental operations you need to perform:
1. Produce a new cursor pointing to the beginning of the sequence.
2. Advance the cursor and determine if we've reached the end.
3. Get the value at the current cursor position.
In most languages, the last two operations are defined as methods on the cursor itself. That means for any given sequence, it needs to define a cursor type specific to that sequence (and storing a reference to it), and you have to allocate that object at runtime, mainly so that you can use it as a dispatch token to find its implementations of those two operations.
That's kind of lame. In Wren, it desugars a little differently:
{
var __iterator = null
while (__iterator = sequence.iterate(__iterator)) {
var i = sequence.iteratorValue(__iterator)
print(i)
}
}
The first two operations are combined into a single iterate() method that uses a "null" parameter to mean "produce a new cursor" and any other parameter to mean "advance". If it returns null or false, we're done with the sequence. Otherwise, it returns a cursor pointing to the next element in the sequence.To get the value at the current cursor position, we call iteratorValue() on the sequence passing the cursor in.
This means the cursor object is pure state—it's just a bit of data that gets passed repeatedly to the sequence. The sequence itself has the dynamic dispatch of the methods for its own iteration protocol.
What this means in practice is that for simple things like lists, you can use a dead simple cursor type: a number.
The implementation of the protocol for lists looks pretty much like this (except its actually in C in Wren):
class List {
iterate(iterator) {
// Start the at first element.
if (iterator == null) return 0
// Stop at the end.
if (iterator >= count) return null
// Advance.
return iterator + 1
}
iteratorValue(iterator) {
return this[iterator]
}
}
Since numbers are unboxed in Wren, it means many sequences can be iterated without any dynamic allocation at all. It makes it easier to implement the iteration protocol because you don't always have to define a class that stores a reference back to your sequence.Wren is dynamically typed, so you don't really have to think about explicit boxing or unboxing.
> Why not have a separate method on the sequence to define the initial value?
Yes, I am definitely considering that. There's no real reason why that needs to be combined with the method to advance, and I'm honestly not even sure why I originally designed it that way.
In case it’s the latter, I’ll just point out that you can have zero-cost abstraction because, in C#, `foreach` is pattern-based: An object is enumerable with respect to `foreach` whenever:
1. It has a public method named “GetEnumerator” of type ‘E
2. 'E has a method named “MoveNext” of type () -> bool and a property named “Current” of type () -> ’T (where ’T can be any class (including those that inherit from System.ValueType.. i.e. a struct)).
While it is the case that `yield return` does result in allocation for a reference type, it’s also true that this is easily remedied should it become a problem: In my experience, it only takes about 5-10 minutes to implement a zero-cost iterator as I described above.
Many of the BSL collection classes (e.g. System.String (as an IEnumerable<char>) and System.Collections.Generic.List<T>) use the zero-cost abstraction.
I read from your blog that you are self-taught programmer. Would you mind sharing something about yourself? In particular, I'm interested in area like what do you do for your day job? Is it programming related? What resources did you come across that you find particularly useful in making lily? Anything would be appreciated!
As for me, I'm not so sure what to share. I'm not all that exciting. Most of the time I have I spend on Lily. Once in a while I take a break. But I don't get out that often. Lily's nice in that it's a place for me to do something and feel like I make a difference.
My day job? For most of the time that I worked on Lily, I was working part-time at [redacted] as a Maintenance Assistant. My favorite memory was talking shop with a guy there who had some computer knowledge. He asked what I had done and I told him that I had just written a garbage collector and that it was so awesome because it took just a week and it went well.
He answered me saying something to the effect of "Well of course you wrote a good garbage collector, you're Maintenance! That's what you do!" We both had a good laugh at it.
I've never had a job doing software development. I recently left [redacted] to pursue a job in software, but haven't had much luck with it. I'm still holding out hope though.
So, no, my job isn't programming related.
Resources? My strategy is that when I get up to something new, I do all of the research that I can both in the topic itself, as well as how other languages do it. One example would be Exceptions. I didn't know how to do that, so I started researching how Python did it, how Java did them, Ruby, etc. I started thinking of what I wanted, and also what the goal of having them in my language was. I also made sure to search through Haskell, Scala, Rust, and OCaml since they don't use exceptions as much. The point being that I wanted to know what languages do when they tend to not use exceptions.
Rather than starting off as "This is a good idea", I instead approach it as, "I think I would like X. How does that typically get done?"
For resources, the most helpful thing I've read has been ESR's guide on struct packing:
http://www.catb.org/esr/structure-packing/
I'm afraid not much else comes to mind though.
Coding itself is fun, yet it is always interesting to know the guy behind software. It is a tough road in making a new language and you have invested 5 years on it! That determination is something I hope could achieve :)
You know, it wouldn't surprise me in the least if you're the only developer who ever created a language and whose day job is not writing code.
Hat's off to you, seriously impressive.
As someone who's self taught and gainfully employed let me know if you want chat. I know quite a few other people who've gone non-traditional routes and I'd be happy to provide some insights.
I love this comment.
I'm self-taught too, and I really enjoy reading about other self-taught people doing great things. There are a lot of barriers for us (not all of them justified), and I empathize with the struggle.
Really sad to hear that you haven't gotten a software job yet. Usually I recommend that folks do enough open source work so that they can unquestionably prove that they can program; more solid proof than the average CS student can provide. Of course, this isn't always an option; as not everyone has that kind of time :( In your case you have written Lily, so this shouldn't be necessary either.
What were your interviews like? Were they all "frob a linked list"-type? I've noticed that most self taught people (incl me) don't bother with these things. They know how to frob a linked list, but are not experts at it. Which makes sense because there are a lot of more fun things out there to get better at :)
If this is the case for you, see if you can avoid such interviews; look for companies that ask more development-y questions. One such company I have had good experience with interviewing with Microsoft, and of course my current employer (Mozilla) asked good questions at my interviews. Of course, small sample space of interviews, ymmv :) If you need I can probably dig up more such companies from the experiences of friends in similar situations. Asking HN directly might also turn up some interesting opportunities.
Keep up the good work! :D
I tend to subscribe to the philosophy that language designers should be as strict as possible; choose the "best" syntax for doing something and only allow that unless there's really compelling reasons for alternatives.
cats.map{|c| c.name} |> print
Which is more readable to me than print(cats.map{|c| c.name})
It's a little jaring that it has both Cat(arg) and Cat.new(arg) syntax. But I wouldn't want to judge that without learning about the semantics that it emerges out of.If you're grabbing the constructor as a function, for example, then it's a bit more sensible to use Cat instead of Cat.new. I don't know...it seemed at the time neat to allow a shorthand way of constructing something, but it's kinda wonky.
This is refreshing from other newish (cough golang) languages which pick the "wonky" path, but go on to defend it tirelessly. Keep up the good work.
On the other hand, Lily is a type-safe language with generics. The kinds of "metaprogramming" that you would try to do with Python classes-as-objects might be done in different ways in Lily.
More theoretically, the whole point of types (as sets) was to was to be a qualitively distinct layer of things that could not be mixed up in logically paradoxical combinations with other objects. That abstruse point of logic might or might not have practical implications for a real-world programming language.
My immediate reaction to the name "Lily" is positive, evokes a sense of calmness, and suggests an elegant, well-structured set of tools.
Regarding constructor syntax, probably "Cat.new()" is more common or conventional. I kind of like "Cat()" as it's less verbose, noisy. However I see what you mean about allowing classes as values, if not entirely clear to me how that feature would absolutely demand one syntax over the other.
I agree with other comments, Lily is an outstanding one-person effort! That its creator is self-taught only adds to its charm.
The piping is more an example of how piping works with everything and that, yes, the new method really really is a method instead of some magical keyword.
But I stand by piping: https://lorgonblog.wordpress.com/2008/03/30/pipelining-in-f/
So is the .new() method in that case an alternate constructor (i.e. constructor code will not run only the contents of the new method will) or is it a method being invoked after the constructor code runs?
And no, AngelScript REALLY does not count as anything interesting. (unless you really really like C++)
* The challenge of it. Lily's one of the few things that I've done where I feel really challenged, and where I can actually make a true difference in it. It gives me something to do, something that I can always be refining and thinking about.
* I -strongly- believe there is a market for this. I want to see this used in game development, used in server development, and used standalone. It's really, really fast scripting language with static typing that brings a unique blend of features that allow for terse code. I want to eventually port this to Arduino, just to say that I did it (Lily boots in 6K with 64-bit pointers, so I think it's possible).
* It's something that I can put at the top of my resume.
* I balance what I do. You might see me spend a week doing plumbing work in lexer, and the next I jump around and patch different places in emitter. I try to not stick in any one area too long. If I do a feature that's difficult and takes a long time, I balance that out with what I term "candy". Candy, in this case, being something that's more immediately visible. I added the constructor shorthand syntax shortly after doing type erasure. Type erasure took a month, and I was a little burned after that. So I balanced it out with a feature that was easier, and had a more visible effect on the language.
* Once a week, I'll go over what I've done, and sort of pat myself on the back, and also think of "Alright, X is done..." and it always goes to "So what's next week?" There's never been a "I'm done". Honestly? The first two years were the hardest, because I didn't have a product that I cared much for. These last three have been easier, because I'm excited about what I'm doing, and there's a more broad spectrum of what I can do.
* You all. No, seriously, I'm finding myself more often talking to different communities about what I've done. I talked to 4chan's /g/ last night. Sometimes I talk to different boards on Reddit, sometimes I try to talk here. I tried Slashdot once, but they didn't bother to even approve the article. Some people give crummy comments, but seeing the new stars, seeing people say they're willing to try the language, that means a lot to me. I really enjoy engaging people when it's over text about what I've done, since I'm comfortable when talking over text and I don't get RL opportunities to talk about what I do hardly ever since I don't know anyone personally RL who does coding for a living, and I'm a pretty stay-to-myself person.
It's the game scripting language I never knew I wanted. I was going to roll a custom Lisp thing for a game that I'm considering, but I may as well just use Lily instead.
>>I want to eventually port this to Arduino
The MicroPython(a python version for microcontrollers) guys are paid by the european space agency(ESA) to create a version that runs on a satellite. I think your "tag" idea for what needs garbage collection could really help them make python time deterministic(and more memory stable) which is great for a satellite and crucial for mcu's. Same for other ideas.
So i think they would appreciate talking with you about that ideas, since your languages share many similar goals.
Thanks in advance, my self esteem.
I'm a couple of weeks away from the end of a three month release window, so I've (for the moment) given up on trying to finish the apis. But I'm not far from having them done. A month, maybe?
But after that, I'll write a guide on how to embed Lily, and how to extend it.
For the moment, your best bet if you're creating a standalone package like postgres is to refer to how postgres structures the dynaload table. If you're looking to embed the interpreter, it's awkward and hard.
Once this release cycle ends (July 10, Lily's true 5 year anniversary), it's the first thing on my list. I want to give people a solid api so that other people can start building stuff and not have to worry about me breaking it.
First the api, then the tooling, then documentation. You've got my word on that (and HN won't let me delete this, on top of that).
Anyway, somebody hire this guy. I'm sure someone that can do this is qualified to make web CRUD for lots of money (should he so desire).
• Have you considered supporting Lisp/Scheme-like or Rust-like macros? or Rebol-like DSLs? (though I suppose the latter are probably strongly tied to Rebol's minimalistic syntax)
Macros are a hard no. DSLs, maybe. For the latter, I could see allowing a pluggable import handler through Lily's options. But I'm reluctant to commit to a yes or no on that at the moment, since I'd rather get over the hump of api not being fully fleshed out.
Out of curiosity, can I ask for your reasons about macros, if you have them fleshed out enough? Not really sure what you mean by "pluggable import handler" in this context, though. Tx!
Also, as Larry Wall said: everybody wants the colon, so it is an extremely useful symbol elsewhere! A shame to waste it.
That being said, this is by a long shot the most tasteful little language seen in a long time.
Impressive work! I think this is the first relatively conventional language that I've gotten really excited about in a while. It's a really nice blend of features and a quality implementation. Definitely going to use it next time I embed a language.
I won't say absolutely no to it, but if it were to be added, it wouldn't be right now. There's too much api that needs doing right now.
I'd have to have a long, long think before adding operator overloading. It's got uses for math, but thus far I've been reluctant to add features that can be abused or used improperly like native class dtors or finally.
I know that there are intrinsics for C, but they are non portable. Maybe except NaCl [2].
Also is concatenation of strings only done through usage of $"^(variable)" syntax?
[1] https://www.python.org/dev/peps/pep-0465/
[2] https://developer.chrome.com/native-client/reference/pnacl-c...
At the moment, I'm not interested in adding vectors or SIMD. It's something I might try further down the line, but not now.
I've seen the complaint that operater overloading allows abuse, but most the examples I've heard seem farfetched.
When ever I discover a new language, I try to find out why it was created.
Why did you make Lily? Are you aiming to solve any specific problems, or is it a fun project you wanted to do?
I started Lily to be an alternative to PHP. The first commit was adding templating support, and it was a while before the language could be standalone.
I had two requirements when creating and designing Lily:
One, that the language had to be interpreted. I didn't want to offer people an alternative to PHP but then tell them that they would need to repeatedly run compile passes over it. Facebook has HipHop, and it transforms PHP into C++. I wanted a language that could be used like PHP, but having a faster interpreter wasn't enough.
The other requirement was to have a statically-typed language. I believed that a statically-typed language would lend itself to better transformation into a lower-level language. In theory, an llvm frontend might also be writable that took Lily as input.
I wanted Lily to outperform PHP both when interpreted, and when compiled. Over time, the focus broadened once I realized Lily's low memory usage, and how fast the turnaround of the parser is.
My mom, from time to time, mentioned that she really liked the name Lily. After giving it a lot of thought, I said, "You know, why not go with that?"
I've grown to like it.
How does one deal with low coupling? For instance, in PHP I would have used interfaces to decouple collaborators.
My plan is to tackle traits in a few months, so long as everything goes well. But I want the above stuff done first, so that when I do traits I can start thinking about higher kinded types, trait inference, and the like.
print($"My name is ^(@name).")
I don't understand why anyone would still require special notation for interpolated strings, when we have the awesome and unambiguous string interpolation syntax introduced by Swift (granted, this project was started in 2011, before Swift).> Reference counting has the benefit of giving consistent, and more easily measured performance. There's no fear of the garbage collector suddenly leaping into action right at the worst time, or having to tune the collector to minimize pauses.
LoL. Refcounting can lead to just as long pauses as GC, if your reference is the last reference to a huge object graph.
You're right about reference counting being able to pause. But that's something you're more able to control. If you're doing game dev, you can say "I won't assign to references that might be big" during frame critical code.
With garbage collection, you're more or less "Run the gc before, hope it doesn't trigger during that critical spot." or the alternative of fiddling with the gc/manually firing it off so it doesn't stop your world when it's a bad time to stop the world.
Why should the swift syntax be considered the default now? "My name is $blah." has been the de-facto standard for ages.
Personally I like interpolation sigils since in most cases you don't actually use it, and languages like Scala allow you to define custom sigils with custom behaviour. For example, Slick defines a sql"SELECT * FROM table WHERE username=$username" syntax that does not cause SQL injection issues.
In fact, it's the first time that I've seen interpolation with round parentheses, rather than curly braces (which are used in javascript with $ (es2015 template literals), python, bash with $, ruby with #, groovy with $, etc... )
> LoL. Refcounting can lead to just as long pauses as GC
He didn't say more performant, he said consistent
(2) It's only consistent if you always know what your pointers are pointing at, and whether your pointers are the last reference to it... but then you can just use delete, right?
gcc -Wall -x c - <<EOF
#include <stdio.h>
int main(){ printf("\(hi"); }
EOF
So it's perfectly reasonable to write it and not mean interpolation (maybe they meant "\\(", but that's another matter)(2) A sequence can be sorted without you knowing which elements are inside it, A computation can be known to terminate without you knowing (yet) which value it will yield.
Similarly, you don't need to have internal knowledge of your code to know that you'll be able to rely on RC consistency in the future (otoh I guess that something consistent is not necessarily the same as something predictable)
Even if it’s accepted it’s not valid, at least in the C99 standard.
You're both wrong. String interpolation is just a very thin syntactic layer. It's easy enough to write
printf("Half of %f is: %f5.2", someVal, someVal * 0.5)
instead of print("Half of $someVal is: ${someVal * 0.5}")
and a lot easier for a programming language to parse, without needing to feed the parser into the lexer. And you also get easy formatting of the decimal (e.g. %f5.2), whereas string interpolation (in Swift, Apache Groovy, etc) requires a separate call, e.g. print("Half of $someVal is: ${format(someVal * 0.5, "%f5.2")}")https://msdn.microsoft.com/en-us/library/dn961160.aspx#Ancho...
$"Name = {name}, hours = {hours:hh}"
var s = $"hello, {name}"
System.IFormattable s = $"Hello, {name}"
System.FormattableString s = $"Hello, {name}"
$"{person.Name, 20} is {person.Age:D3} year {(p.Age == 1 ? "" : "s")} old."Laughing out loud is fine I guess but it adds nothing to this discussion as that paragraph would easily stand on its own or even be better without.