13 ms·
Passerine: A small extensible language designed for concise expression
- haolez 6y agoLooks nice. I've been recently looking for an expressive language to make an experiment combining two approaches to "cloud native" programming: something like Ballerina[0] combined with something like Dapr[1]. For example: when I instantiate a queue, I get one from my cloud provider, or my local Redis, or my custom C++ implementation. That way, my code would represent my intent, and the actual building pieces could be switched from environment to environment. Dapr is already pretty close to this, but it's still a little verbose and low level compared to "normal" local programming. Maybe I'll experiment with Passerine for this, since it looks easy enough to tweak with Rust. [0] https://ballerina.io/ https://ballerina.io/ [1] https://dapr.io/ https://dapr.io/
- CuriousSkeptic 6y agoYou might find this “New Directions in Cloud Programming” paper interesting http://cidrdb.org/cidr2021/papers/cidr2021_paper16.pdf http://cidrdb.org/cidr2021/papers/cidr2021_paper16.pdf
- swagonomixxx 6y agoFrom a glance it seems to be mostly Haskell-ish but with Scheme style macros.
- ojnabieoot 6y agoHmm, at a somewhat longer glance, I would say it’s very Scheme-ish in its semantics but only superficially like Haskell (and really more like ML generically). Specifically, despite algebraic type definitions, Passerine is dynamically-typed with an emphasis on structural/“duck” typing. The author plans to add a Hindley-Milner type checker but the language does not seem to be “type-centric” in the way I think of Haskell - types will be added for verification, but not as a near-first-class programming construct. I get the impression it might be more like mypy and that runtime type errors will still be a thing. From the perspective of a very young functional programming language, it is very tricky to balance a powerful type system with powerful macros and I suspect that macros are the priority. The developer also cites the Erlang VM as a major inspiration in terms of a dynamic “expect things to fail and handle it” approach. Still looks very interesting! I love Scheme (despite having become a static typing curmudgeon) and will be checking out this project.
- slightknack 6y agoAny type system with dependent types (`where` in Passerine) is undecidable, meaning error reporting for these types has to be moved to runtime. There's a quote from someone somewhere, and it goes something like this: > Ocaml is still trying to develop a macro system that Racket folks won't laugh at... and Racket is still trying to develop a type system that doesn't trip Ocaml fans into a tizzy. Given that Passerine takes cues from both ML and Scheme, I've kinda given myself the Sisyphean task of bridging this divide. I'm still trying to find the right balance - I did macros first, so I can say I know more about how they work in Passerine - but I hope with a complementary static type system Passerine will become that much more useful.
- drdeca 6y agoCan you elaborate on the undecidability bit? The task that isn't decidable is, what, "Given a program, determine whether the types are respected in all assignments" ? I'm pretty sure that the calculus of constructions has dependent types, but wikipedia says that it is strongly normalizing, and I'm pretty sure that determining whether a program is valid in CoC is decidable? (Right? I could be wrong..)
- slightknack 6y agoI'm no type theory expert, but if you can describe natural numbers as: Even (n: Natural) | n % 2 == 0 Then what's stopping you from doing: Undecidable _ | loop {} I'm not sure, but I think that's because strict dependent language systems like Pie (similar to CoC) use induction over the structure of a type for decidability. If I had 'the little typer' on hand, I'd elaborate more on this point. I think there's some relation between undecidability and the type Absurd, but I'd have to look into it more.
- slightknack 6y agoOh, I think I just confused refinement types with dependent types. The above still stands though.
- toyg 6y agoI guess this won’t get much use in Italy, where the name is a classic slang word for vaginas...
- ozten 6y agoIsaac Clayton @slightknack High-school student and aspiring Software Architect. Enjoys Language Design, Graphics Programming, and Machine Learning. Working at @vrtbl on Passerine.
- brundolf 6y agoJesus. What have I been doing with my life? Edit: Here's another super impressive project by this person https://github.com/slightknack/keikan https://github.com/slightknack/keikan
- ashkankiani 6y agoPeople born of the highschool age have had access to computer at least 10 times more powerful than when I was the same age, and with a lot more material on the internet and a more mature field as a whole. It would be stupid to feel bad by comparing yourself. He mentions that he's been programming for "a decade" by high school. There were no computer science classes in high school in America when I went 10 years ago. So he seems to have had good exposure from his parents as well. That said, he does seem to be taking full advantage of his advantages. He's the kind of CS major I wish I had encountered when I was in school. Too many of them just seemed to slip in for the 6 figure salaries on the rise. Also it's pretty nostalgic how obvious the wording on his site is as being poised as a resume. It reminds me of my career start. Though with just this PL project, his resume is already stronger than most his age.
- gonzo41 6y agoNot to be too cynical. The real challenge is to see if he's able to avoid using all those skills to make people click on adds.
- eevilspock 6y ago"The best minds of my generation are thinking about how to make people click ads. That sucks." ~ Jeff Hammerbacher, fmr. Manager of Facebook Data Team, founder of Cloudera (one can argue that indirectly that is what every employee of Google does)
- brundolf 6y agoSomething I've been wondering is whether a Rust-embedded language has a place for making the "glue API server"-writing experience better. Rocket is an amazing library, but Rust's strictness - much as I love it - still slows down the iteration phase quite a bit. Imagine being able to hack out an API, twiddling JSON and reshaping data, in something like Passerine, and then step the different pieces down to Rust code as they solidify
- smt88 6y agoYou can hack out an API in Open API Spec (I recommend Stoplight as a GUI for that) and then stand it up as a mock server for initial UI dev. If the language has a code generator written for it, you can then generate method stubs and types for your server. If not, you can still use your spec as a validation document that you feed into a library before responding to a request.
- brundolf 6y agoThat's not really what I mean... I mean that throwing together an API, even one with a bit of business logic, is still nontrivially easier in Flask than it is in Rocket (despite Rocket being the gold-standard for this in the Rust world). If we really want the Rust ecosystem to compete with, say, Go when it comes to regular web APIs, I think we're going to need more than just a framework. An embedded scripting language could be the ticket
- slightknack 6y agoI'm currently thinking of ways to make macros that serialize Rust structs to Passerine values (and vice-versa) a la serde. I think being able to hack out an API in a high-level language and then tossing it down to something like Rust for the low-level perf gains is a great idea. Furthermore, I'd like for this to be possible: - project |- Aspen.toml |- src ||- ... passerine code |- ffi ||- ... rust code Where Passerine could just wrap and call the Rust code automagically. This is still a ways off though...
- brundolf 6y ago
- xfer 6y agoIs there an easy way to import rust data types into passerine(maybe via some derive macro)? I understand the author is exploring the PL design space, but for an embeddable language a good interop story is what sets them apart.
- slightknack 6y agoThis is actually something I've been thinking about a lot! I'm not sure about the time frame, but ideally `passerine` (the crate) would provide a derive macro (similar to serde) that could serialize Rust structs to passerine, and vice versa. Given that Passerine has an algebraic data type model similar to Rust, I think they would overlap quite nicely.
- ledauphin 6y agoDespite new language fatigue, this does look interesting. A few things that stand out: 1. use of Exceptions. These get so much hate these days, but the articulation here of the reasons for allowing them is better than most of the Hacker News comment threads about them. I might just link to this section of Passerine's docs the next time somebody tries to tell me how Result<> is the only thing any programming language needs. 2. The use of fibers/coroutines as built-in error boundaries. This is such a pleasant natural extension to the Erlang proposal I find myself wondering why so few of the languages I'm familiar with have taken the same approach. Presumably I need to get out more. 3. Not sure what to think about the left-associativity of function calls, nor the lack of explicit call syntax. One of the things that bothers me about Ruby is the extra work you have to go to to pass functions as arguments to other functions. Naively, lacking explicit call syntax seems to me like a premature optimization. I'd appreciate someone's perspective on these two (separate?) points.
- scythe 6y ago>2. The use of fibers/coroutines as built-in error boundaries. This is such a pleasant natural extension to the Erlang proposal I find myself wondering why so few of the languages I'm familiar with have taken the same approach. Presumably I need to get out more. This is done in Lua, although it has a divergence between `pcall()` and `coroutine.wrap()`.
- brundolf 6y ago> The use of fibers/coroutines as built-in error boundaries Isn't this roughly what JavaScript does with Promises?
- slightknack 6y ago'roughly' ;)
- sterlind 6y agomy issue with Promise-style exception boundaries is the lack of type checking or explicitness, especially since `catch(Exception)` is discouraged in C#, specifically (since e.g. you probably want to panic on null refs but handle a socket exception.) in my ideal, exceptions could only be thrown/caught at the async/await boundary because only there should you have real side effects, and there should be structure, but at that point I'm reinventing Haskell's monads.
- peanut_worm 6y agomight try this out just because I like birds
- xixixao 6y agoNot to discourage, but I think it's worth writing: I too was very much in love with conciseness, and spent a lot of my time with CoffeeScript, and had a blast. That said, if you either - Work with another one person or more - Want to come back to a code you wrote some time ago conciseness is not your friend. Second, new language is really, really, really a lot of work (I know, I spent a year building one). It'd be awesome if more folks invested their energy into improved tooling for existing languages. I desperately want to be able to select a block of Rust, then run a VS Code command and get a new function. I want type-based completion, so that types are filled in for me on conflicts. Better errors and hits for resolutions. And a better playground for Rust would be nice too. Basically most of the things I had in Golem[0]. That said, building a new language is much more fun than trying to wrestle with VS Code dev process. So enjoy the ride! :) [0] https://www.youtube.com/watch?v=HnZipJOan54 https://www.youtube.com/watch?v=HnZipJOan54
- slightknack 6y agoI understand the issue with conciseness and readability. I think comments are underappreciated when writing code: not only can you explain what some code does, but how it does it, and why it does it that way. This doesn't have to be documentation, per-se, but it's quite nice when it is. > Second, new language is really, really, really a lot of work (I know, I spent a year building one) I know what you mean ;P. I'd say writing a programming language compiler/interpreter is probably the easiest thing about making a new programming language. Tooling, adoption, libraries, etc. are the other 99%. > It'd be awesome if more folks invested their energy into improved tooling for existing languages. I agree, but I have two rebuttals for your following point. I'll start with the more logical one: 1. As a hobby project, learning the internals of how a compilation pipeline works is much more fulfilling and generally applicable then wrestling with whatever API a VSCode developer decides to chose. 2. And here's the more irrational one: If Guido just decided to work on tooling for C, we wouldn't have Python. I'm not saying I'm the next Guido (not even close, haha), but there are some language features that I wish were more mainstream, and what's a better way to show that certain features are a good fit for a language than to make a language with those features? To make an omelette, you have to break a few eggs, I guess. (P.S. Golem looks pretty neat, congrats!)
- pkulak 6y agoWell, I think it's really cool. Gives me a Haskell vibe, while being far more approachable.
- birktj 6y agoI really like how this has zero dependencies [0] [0] https://github.com/vrtbl/passerine/blob/master/Cargo.toml https://github.com/vrtbl/passerine/blob/master/Cargo.toml
- slightknack 6y agoI've found that most small crates can be replicated in an afternoon. It's much more fun (and performant!) if you do it yourself. :)
- didibus 6y agoNever understood why languages like this don't just adopt full Lisp syntax. I'd have been much more interested.
- lifthrasiir 6y agoBecause lisp syntax is never something everyone appreciates. See, for example, Dylan.
- timbit42 6y agoDylan is less appreciated than Lisp.
- Garlef 6y agoYou could just read linear = m b x -> b + m * x as (linear = m b x -> b + m * x) and assume this to be an expression given to some kind of macro ;)
- slightknack 6y agoMy goal was to replicate lisp without the redundant parenthesis. There are a few conventions common in lisp: expressions usually occur on a new line, blocks begin with, well, `begin`, there is spacing between definitions, macros operate on forms, etc. etc. I thought, why not use the conventions when writing a language to the advantage of the notation used to represent that language? A chain of symbols actually creates a `form` like a lisp list. So `a b c + c d e` is the same as `(a b c) + (c d e)`, which in list would be `(+ (a b c) (c d e))`. Additionally, newlines can be used to separate forms. So you can imagine that each expression on a new line is grouped in parenthesis - except in the case of operators, which can be split for legibility. Some more: If you do group an expression with parens explicitly, you can split it across multiple lines like in lisp: hello (this is) a form (hello (this is) a form) (hello (this is) a form) I that with enough work, one could use Passerine's macro system to turn the language into a lisp. Heck, you can already go the other way and turn it into this: syntax function name(args) body { name = args -> body } function thingo(x, y) { x + y * 2.7 } Notation is a powerful tool. Lisp is one two. It's hard to sneak a lisp past the eyes of a general population, but I think that the core of lisp: code as data, 'the Maxwell's Equations of Software' needn't be forever tied to s-expressions.
- deleted 6y ago[deleted]
- bcherny 6y agoSome really interesting design choices here: Haskell/ML style FP base, fibers as a concurrency primitive, both exceptions and ADTs. More than those, I LOVE the macro system. Such a clean syntax!
- slightknack 6y agoThanks! <3
- Garlef 6y agoLooks very interesting. I would love to see an online repl/playground for this, similar to the TS playground (https://www.typescriptlang.org/play/ https://www.typescriptlang.org/play/).
- slightknack 6y agoThe core language has no dependencies, and can compile to wasm, so it's on the docket.
- dang 6y agoIf curious see also Show HN: Passerine – an extensible programming language – v0.8.0 released - https://news.ycombinator.com/item?id=25347140 https://news.ycombinator.com/item?id=25347140 - Dec 2020 (8 comments)
- akavel 6y agoThe github readme seems to mention it's "structurally typed", but I'm still not confident if the types are checked statically or dynamically? Could you please clarify this on he website & readme?
- slightknack 6y agoThat's because Passerine is in a bit of a transition zone. It's dynamically typed right now, but it will be statically typed in the future (the next release actually, fingers crossed). See the FAQ section of the README for more info. Once the type system's in place, I'll definitely update the website/README to match.
- camillomiller 6y agoSorry for the OT comment, but this language name means “small female genitalia” in some parts of Italy. I guess just FYI in case you need to localize. :)
- ppenguin 6y agoLooks really cool! Since I'm just (as a "hobby") starting with Haskell, it looked pretty familiar. (I have no previous experience with ML and Scheme, which is also frequently mentioned as an influence). At the risk of appearing too lazy to first read everything about it before asking this question (maybe the appearance is true ;): what are, in a nutshell, the advantages over Haskell and main differences in terms of "not re-inventing the wheel"?
- slightknack 6y agoI think the README addresses this question well, if you're familar with Haskell and know what to look for. The main differences are the macro and the fiber system for metaprogramming and concurrency respectively; Passerine aims to keep functional programming fun, so you don't need to worry about Monads or Lenses or Contraroutines* (jk) before writing a simple program. * This is an inside joke.
- tom_mellior 6y agoSome questions about the FAQ's "What is vaporization memory management?" section: > All functions params are passed by value via a copy-on-write reference. What's a "copy-on-write reference"? Does this mean that if write a function that takes a million-element list, and inside the function I mutate the last element, the entire list will be copied (inside the function, but invisibly to the outside)? If yes, can I write a function that mutates a list in place so that the caller can see the effect? Can I even mutate lists or anything else? I don't see any clear information about whether Passerine has mutable data at all. > A form of SSA is performed, where the last usage of any value is not a copy of that value. Does this mean that any non-last usage is a copy? Or is it "only" a copy-on-write? Can you always tell statically where a usage is a last usage? > All closure references are immutable copies of a value. These copies may be reference-counted in an acyclical manner. Again, are these eager copies or lazy copy-on-writes? How are non-closure references garbage collected? I would be interested in a much more detailed write-up of this memory management technique.
- slightknack 6y agoI'm working on a blog post on the subject, but this is something that needs more justification than an HN comment. To answer your questions: > What's a "copy-on-write reference"? A copy on write reference (CoW) is a pointer to some immutable data, that when written to, is copied to a new memory location first. > Does this mean that if write a function that takes a million-element list, and inside the function I mutate the last element, the entire list will be copied (inside the function, but invisibly to the outside)? Only if you use the original list later: big_list = [ ... ] new_big_list = mutate_last big_list print big_list This would copy `big_list` because we use it in `print` later. If you do not use big_list later: big_list = [ ... ] new_big_list = mutate_last big_list Or reassign the variable: big_list = [ ... ] big_list = mutate_last big_list the original list is not copied, because this is the last usage of a value, and last usages pass the actual value rather than CoW, allowing the program to mutate it directly. This is what Functional but in Place (FbiP) means - You can write code in a functional style, but when executed it will mutate data in place. > Can I even mutate lists or anything else? I don't see any clear information about whether Passerine has mutable data at all. So, this is less about Vaporization and more about Passerine. Passerine does not have mutable data, only mutable references to data*. So the reference can change (or like in a record, the reference in a field can change), but not the data itself. Because of FbiP, however, mutations are optimized to an actual mutation rather than a copy and an overwrite. * This is needed if one wishes to extend HM type inference to support mutation, IIRC. > Does this mean that any non-last usage is a copy? Any non-last usage is a potential copy. If the function you pass it to does not mutate the data, no copy will be made. Last references also include reassignments, so something like: big_list = [ ... ] for x in 0..100 { big_list = big_list.append x } Does not make 100 copies of `big_list`. > Again, are these eager copies or lazy copy-on-writes? These are eager copies, and immutablility is enforced by the language itself. This tradeoff has to be made, because mutable references allow for the construction of cycles. These references are reference counted if a copy of the closure is made or another closure closes over the same value in the same scope. It's safe to do this because these references are immutable, and when all closures referencing them go out of scope, they will be dropped. > How are non-closure references garbage collected? Non-closure references are 'garbage collected' when they go out of scope. Vaporization basically ties everything to the stack, and prevents values stored on the heap from becoming dissociated with it. > I would be interested in a much more detailed write-up of this memory management technique. Thanks for the interest! There's still a bit more formal verification to do, which is why I left this broad claim to the FAQ - we're rewriting the website, so the mention of it there is a bit outdated. Given that it was just asked here, it looks like I need to extend the FAQ some more, given how frequent of a question it is. ;P Have a nice day!
- hardkorebob 6y agoI ran your script and this is what errors it had: Would you like to proceed with the installation? (y/N) /dev/fd/63: 48: read: Illegal option -n /dev/fd/63: 50: /dev/fd/63: [[: not found Im new to programming. In any case it did install completely. Thanks for the new language. Looks real cool.
- slightknack 6y agoWhat operating system do you have? It seems like your `read` command does not support the `-n` argument - but that's not the point, I really need to work on removing installation friction. If you have git and cargo installed (needed anyway), you can try: # project structure cd ~ mkdir .aspen cd .aspen mkdir bin # download git clone https://github.com/vrtbl/passerine git clone https://github.com/vrtbl/aspen # build cd aspen cargo build --release mv target/release/aspen bin # source echo "export PATH="~/.aspen/bin:$PATH" >> ~/.profile Restart your shell, and it should work. Type `aspen` and hit enter and see if you get a help message with version 0.4.0. If you have any more issues, reach out on the Discord server so we can reach a more permanent solution (if you haven't already).
- hardkorebob 6y ago[[ is a Bash feature. The command for install uses POSIX sh. Maybe that's the problem. I use Debian and Bash. Thanks for the reply.
- slightknack 6y agoAh, It seems like like my #!/bin/sh suspiciously supports bash features... I'll have to update the install script. Thanks for pointing that out!
- Seylerius 6y agoThis memory management looks pretty shiny, but is there any way to get down to manual allocation as well?