HNHacker News
TopNewBestAskShowJobs

slightknack

311 karma · joined September 2, 2019

A software architect who likes language design, RL, and impressionist paintings.

[ my public key: https://keybase.io/slightknack; my proof: https://keybase.io/slightknack/sigs/WcaeY2T7Yk1zxtpKL0lKKQ_8EfoCF5pReRxzOEDSUcI ]

submissionscomments
slightknack··on Passerine: A small extensible language designed for concise expression
> Comments should explain the why, not the what or the how. Obviusly that are exceptions, almost all of them around the really algorithmic parts of the code, something I have to write less than once per year (backend software is not that clever.)

Although I agree that choosing clear variable and function names is usually enough to explain what code does, I'm just not entirely convinced that concise code becomes harder to read with multiple people or after coming back after some time. I feel that communication and documentation is generally the best way to get someone up to speed.

By concision, I mean something closer to terseness or brevity: exactly enough to understand what's going on, without anything superflous. Here's a classical concise python one liner:

    def flatten_list(t):
        return [item for sublist in t for item in sublist]
This flattens a list of lists into a list. But if you're actually coming back to the code either having never seen this comprehension or forgetting how it works, a little comment never hurts:

    # iterates through each sublist to flatten a list of lists into a single list
Some may say this is redundant, but it's useful in more complex circumstances too. If you're working on a machine learning pipeline, say, it can be a quick way to block out some code.

    # normalize all the input data
    # generate embeddings
    # train until error doesn't improve for 100 epochs
    # run the net on our test set
(This is my third most favorite programming trick: pretending that something exists, then building it out*)

These examples are besides the point though. Terse code doesn't have to be illegible or hard to comprehend; it's all about finding that balance: no less, no more.

* My second most favorite programming trick is building a small set of tools and using those to redefine the problem.

> If it's not enough probably those functions are doing the wrong thing, maybe more than one at once, and must be redesigned.

Often I'll come across old code, think, "this can be written much more efficiently if I do it like this" and then half way through refactoring remember that I did it the first way because I already thought through the problem and realized that the first way was better and but wait maybe if I do this then that and so on...

It's easier to leave a quick comment just explaining why code is the way it is, which is all I was suggesting.

slightknack··on Passerine: A small extensible language designed for concise expression
What 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).
slightknack··on Passerine: A small extensible language designed for concise expression
I'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!

slightknack··on Passerine: A small extensible language designed for concise expression
I 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.

slightknack··on Passerine: A small extensible language designed for concise expression
Oh, I think I just confused refinement types with dependent types. The above still stands though.
slightknack··on Passerine: A small extensible language designed for concise expression
I'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··on Passerine: A small extensible language designed for concise expression
I've found that most small crates can be replicated in an afternoon. It's much more fun (and performant!) if you do it yourself. :)
slightknack··on Passerine: A small extensible language designed for concise expression
The core language has no dependencies, and can compile to wasm, so it's on the docket.
slightknack··on Passerine: A small extensible language designed for concise expression
Thanks! <3
slightknack··on Passerine: A small extensible language designed for concise expression
That'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.
slightknack··on Passerine: A small extensible language designed for concise expression
My 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.
slightknack··on Passerine: A small extensible language designed for concise expression
'roughly' ;)
slightknack··on Passerine: A small extensible language designed for concise expression
Any 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.

slightknack··on Passerine: A small extensible language designed for concise expression
This 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.
slightknack··on Passerine: A small extensible language designed for concise expression
I'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...
slightknack··on Passerine: A small extensible language designed for concise expression
I 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!)

slightknack··on Passerine: A small extensible language designed for concise expression
I'm glad you found the langauge interesting! I actually started Passerine after reading Bob Nystrom's blog posts on concurrency and iteration (Iteration, Inside and Out). At the time, I was also reading through the thesis on Erlang, and I was thinking how neat it would be to combine Nystrom's thoughts on concurrency with Erlang's processes.

I think that I could answer 3, given I know why I did what I did ;P. So, passing a function as an argument to another function is pretty simple. If it's in a variable, say `bar`, `foo bar` will do. If it's anonymous, say an identity function like `i -> i`, you just need to group it and schloop it in: `foo (i -> i)` or `foo { i -> i }` will work.

Thanks!

slightknack··on Passerine: A small extensible language designed for concise expression
Hey, author of this language here. I love Rust, but what I like the least about `panic!` is the lack of context it provides. I mean, sure, there's `RUST_TRACEBACK=1`, but that's not nearly as neat as a real traceback, with the appropriate context and data to help determine the root of the problem.

I'm still thinking through my design options, because I've been recently reading a lot about Algebraic Effects. I'd hate to have two non-orthogonal features in a minimal language, which is why I'm waiting until more of the language has solidified and I've reached a conclusion about Algeffs before implementing the whole error system.

Thanks!

slightknack··on Show HN: Passerine – an extensible programming language – v0.8.0 released
Not currently, but I don't see why not - the core language is a simple zero-dependency binary.
slightknack··on Show HN: Passerine – an extensible programming language – v0.8.0 released
Thanks for the encouragement! There is so much more I want to explore :)
slightknack··on Show HN: Passerine – an extensible programming language – v0.8.0 released
Hey HN,

Isaac Clayton here! I'm nervous yet very excited to share an early preview of a novel programming language I've been developing for the past year or so. Passerine is an functional scripting language, blending the rapid iteration of languages like Python with the concise correctness of languages like Ocaml, Rust, and Scheme. If you'd like to learn more, read the Overview section of the README.

It's still a ways away from being fully complete, but this release marks the introduction of Passerine's macro system. Like the order of songbirds it was named after, Passerine sings to more than just one tune – this new hygenic macro system makes it easy to extend the language itself – allowing you to bend the langauge to your needs, rather than bending your needs to the language!

This submission links to the GitHub Repo, but there's also a website (https://www.passerine.io) if you'd like to look at that.

Have a nice day!

slightknack··on Ask HN: Who wants to be hired? (July 2020)
Location: Utah, USA.

Remote: Preferred.

Willing to relocate: No.

Technologies: I primarily use Rust (Embedded, WASM, Systems) and Python (Tensorflow, Pytorch, Numpy, Pandas, Flask). I also know C, Java, Go (libp2p), Ruby, ML (Standard ML, OCaml), Lisp (Scheme, Clojure, Common Lisp), JavaScript/TypeScript, and a myriad of other languages/technologies. Needless to say, I know my way around the UNIX command line. I'm a fast learner; I hit the ground running.

Résumé/CV: Available upon Request.

Email: hello@slightknack.dev.

GitHub: https://github.com/slightknack.

Languages: Fluent in both English and Spanish.

I'm Software Architect specializing in ML and Language Design, currently looking for remote part-time or contractual work. I've been programming for the better part of a decade; in that time, I've worked on many systems, including a photo-realistic rendering pipeline, a content-addressed versioned web-framework, and temporal difference prediction for self-driving cars. If you're interested in hiring me, please don't hesitate to contact me.

← PreviousPage 2 of 2