People
Activity
    Nick Johnstone
    @Widdershin
    haha
    William Brandon
    @SelectricSimian
    Another neat thing is that because the existential variable is totally opaque to the outside world, you also get privacy and encapsulation as emergent consequences of this system
    Anyway, so this enables a lot of patterns -- in fact, it enables far too many patterns, such that a frontend developer has to make a lot of choices in how they map their high-level modules to Nickel expressions. But no matter what choices you make, Nickel is capable of supporting pretty much any module or package management system
    So, in your case
    It seems like your external packages are in some sense parameterized by their dependencies, in a way that lets their clients control and vet exactly what they have access to. In Nickel, you can literally model that by treating the external package as a function of its dependencies, and then passing implementations of those dependencies as arguments.
    And in fact, I've been intending since the beginning for that to be the dominant means of representing dependencies in Nickel
    Nick Johnstone
    @Widdershin
    Our designs seem extremely well aligned
    William Brandon
    @SelectricSimian
    So, for example, the absolute top level of a Nickel program is a function that takes in a giant tuple of everything the platform supports -- hundreds of fields worth of arithmetic operations, memory management primitives, core data structures, IO functions (all without breaking purity, of course! :) ), etc
    And yes, our designs do seem well-aligned
    And then the top level is responsible for passing those core primitives, or whatever subset of them it's willing to provide, down to its dependencies
    Now, you can do cool things with this
    The most obvious of which is audit the exact functionality that your dependencies use. So if you don't want to give them filesystem access, just don't pass the filesystem submodule of the standard library
    But you can also, for example, mock out the stdlibs with a test harness
    Or pass a version of the stdlibs where everything has been modified to detect, intercept, and forbid certain disallowed operations
    Such as accessing a file not belonging to a particular filepath, or something
    Nick Johnstone
    @Widdershin
    Yeah, that's also an intention for Forest! including the notion that we might want to scope access to stdlib behaviour
    haha you beat me to it
    or for example, only making http requests to google.com/search/...
    William Brandon
    @SelectricSimian
    Yeah! Exactly
    Honestly I thought the ability to do this was just kind of a weird side effect of the way I was representing modules -- it's really cool to see that it actually has a use!
    Nick Johnstone
    @Widdershin
    I honestly think that there's a good chance we'll look back and wonder how we did it any other way
    Especially if we can address the worst UX aspects of having to inject everything
    William Brandon
    @SelectricSimian
    Anyway, in addition to all that, there's also all the safety that comes from just being a pure language. You can restrict side effects because they're represented in the type system and mediated through args and return values -- which doesn't really operate at the module / import level, but can still be very useful for this same sort of thing
    Yeah! The most interesting challenges with this aren't theoretical or technical, I think, they're figuring out the UX so that it's simple and approachable
    Which, I must warn you, Nickel certainly is not, nor is it intended to be, at least at this stage. I want to build simple and accessible languages, but first I'm building Nickel as an enabling technology for those larger plans
    But anyway
    So that's the dependency injection story
    Now, as for performance
    Nickel uses linear types -- the same trick as Rust and many academic languages before it -- to allow purity and memory safety to coexist with mutation and manual memory management. I don't know if you've used a language with linear types (technically, Rust has affine types), but the idea is beautifully simple: by preventing a value from being in more than one place at once, you can mutate it, or even deallocate it, without anyone noticing, and therefore can't break either functional purity or memory safety
    Nick Johnstone
    @Widdershin
    Yeah, I've done a bit of Rust and read far enough into the Rust book to get my head around their approach to borrowing, it's super elegant
    William Brandon
    @SelectricSimian
    Great! And yeah, I'm a big fan
    Nick Johnstone
    @Widdershin
    I'd like to have a similar approach for forest, and probably with something comparable to the Rc type when necessary
    William Brandon
    @SelectricSimian
    Now, at the moment I have a bunch of sketches lying around for how different memory usage patterns can be represented safely in Nickel. It's not nearly as convenient or concise as Rust's system, but it's based on simpler theoretical primitives and it is in some ways more powerful. And it's all done at the library level, so you can extend it or replace it if you want to, as long as you're willing to write a small amount of unsafe native code (in C, raw WASM, or whatever) to power the whole thing
    Nick Johnstone
    @Widdershin
    Interesting to approach it at the library level, haven't really seen that before
    So far in Forest I've been taking the "write a small amount of unsafe native code" approach
    currently have the world's simplest malloc implementation, and no free
    written by hand in Wasm
    William Brandon
    @SelectricSimian
    I won't go into all the implementation details right now, but some of the things I've figured out how to express in the type system (but not implement in Nickel -- they'd have to be backed by external unsafe code) are:
    • Uniquely owned allocation, like malloc or new -- think Box in Rust
    • Reference counted heap allocation -- think Rc
    • Dynamic stack allocation, like alloca in C
    • Arenas
    • Contiguous array slicing and viewing
    • Shared immutable references to a reclaimable borrowed object
    • Heap-allocated LIFO allocators -- these are weird, and I'm especially excited about them :)
    A heap-allocated LIFO allocator is basically a way to have your very own pet call stack, small and domesticated and entirely separate from the huge and inscrutable native call stack
    And that's really interesting for implementing things like coroutines
    Or futures / promises
    Nick Johnstone
    @Widdershin
    Or actors?
    William Brandon
    @SelectricSimian
    Yes! In principle
    Basically anything asynchronous (although not necessarily multi-threaded, although concurrency should be easy and safe for the same reasons it is in Rust)
    And sort of a related point to all of this is that, as I said, you have a lot of choice in how you implement closures. Which means you don't need an expensive heap allocation every time you call a higher-order function. If you have a smart optimizer, or a language frontend like Rust's that makes these kind of issues explicit, you can very often stack-allocate all the data that a closure carries along with it, in a way that Nickel is fully capable of expressing and proving safe
    Nick Johnstone
    @Widdershin
    That's phenomenal. I was planning on a naive implementation of closures that stores references to the requisite data, but being able to do that without heap allocation would be awesome
    William Brandon
    @SelectricSimian
    Unfortunately, I really have to get to sleep now, but let's talk later! :wave: :)
    And again, thanks for your interest and enthusiasm!
    Nick Johnstone
    @Widdershin
    Yes indeed, have a nice sleep! It's been great talking to you, I feel like we'll be able to benefit a lot from each other's work
    William Brandon
    @SelectricSimian
    Agreed. Night!