Pony – High-Performance Safe Actor Programming
ponylang.io
ponylang.io
I rather fight the Reference Capabilities (easily the hardest aspect of Pony and probably the number one reason people give up) than to spend endless amount of time CHASING concurrency problems, or even worse, HOPING that I didn't forget some lock/mutex/whatever and ending up having data corruption.
Pony is forcing me to do the right thing, and doesn't allow me to "I guess I could get away with X", and this reassurance is a huge boon.
It is hard to learn, and if you are not in the concurrency space, don't know what a thread is and don't care much for data integrity, then Pony is not for you. The effort you need to put in will not be appreciated by you.
If you have a strong opinion on how things must be done, Pony is not for you, because it won't allow you to follow your opinion all the time, because the opinion is not safe.
Pony is not for everyone, but for those that can think in the Pony model, then it is an incredible tool to ensure your code is correct, safe and performant.
My primary factor was "Actor Model" and I was struggling with Erlang's lack of static types, so that put Pony in the focus. An Actor Model framework on top (I recalled there was one for Rust) had given me "questionable" results with Akka (due to compromises/constraints in the JVM and Java/Scala).
HTH
- Statically verified to be unique (&mut)
- Dynamically verified to be unique (RefCell)
- Behind a lock (Mutex, RwLock)
- Explicitly opt into weaker (but still consistent) guarantees (atomics)
- Some other abstraction built on those primitives (like channels)
Unsafe does exist, but it's something that the code must explicitly opt into, and they rules that must be followed are generally "the same" across the whole Rust ecosystem (rather than just accidentally stumbling into something that is OK in "normal" Scala but breaks some weird Akka rule).
In any case, the language enforces that you are self-consistent: if you use locks as your escape hatch for something then the code won't compile if you try to access it without an active lock.
In other languages, lets say Erlang as an example, actors themselves do not have concurrency concerns. The receive loop/handler just executes everything as though it's single threaded. The code is very easy to reason about. It also applies backpressure in that if your synchronous loop is blocked, your mailbox will fill up.
If you need concurrency, it's done by talking to other actors, which can be processing on another thread under the hood. But the thread part of it is managed and you are not dealing with threads per se, you just know that if you have multiple cores and you send a message to another actor, it can be scheduled to run on another core safely.
If you want to run a bunch of tasks in parallel, you could use a pool of actors up to around the number of cores you have, and the parallelism is at maximum the number of actors in the pool.
Sorry if i'm over explaining, I just wanted to set the stage.
One of the benefits of this arrangement is if something is going slowly you can order the list of actors by biggest mailbox and you can see where your bottleneck is. And if you are using a lot of memory you can just order the actors by the memory usage and you can see where the big state lives.
With akka actors, instead of just dealing with the actors and actor pools, they suggest you make actors non-blocking. The way you do this is with Futures. Suddenly all the simplification of the actor model goes out the window. It mixes an async programming style with an actor model that doesn't need to be async! So you have the negatives of asynchronous programming and very few benefits of the actor model. I realise
How do you identify the bottlenecks of the system? Maybe your execution context is full - actually I'd love to know how people debug their execution contexts in general.
Last I checked, execution contexts would spawn new threads as well, so not only are they heavyweight (compared to erlang processes) but you have the operating system schedule them instead of the thing that knows how to best schedule them which is your language runtime.
But AFA Async, I guess I have thoughts.
> With akka actors, instead of just dealing with the actors and actor pools, they suggest you make actors non-blocking. The way you do this is with Futures.
I don't know how things work in JVM/Akka specifically but on the .NET side the use of async is 'Tolerable'. We have a ReceiveActor that lets you wire up your message type to an async method and it handles all of the stashing/etc internally until the future completes. You have to type a couple extra words but they're the same words you have to type everywhere else you use async.
With the other sugar .NET gives you it's really not too bad. In the system our team built, the main piece of 'blocking' code we have is a call to a C library that does not have any real 'async' bindings. The rest were things like loggers, although typically if 'logging' is blocking either you're logging too much or the rest of the system is probably not in a good state anyway. (edit: FWIW, the 'block' is 80-100ms and constant, we can live with it for our case)
> How do you identify the bottlenecks of the system? Maybe your execution context is full - actually I'd love to know how people debug their execution contexts in general.
Interestingly, Akka.NET doesn't quite have this sort of problem.The default Dispatcher (At least that's what we call the base type) runs on the .NET Thread pool which has a good degree of 'self tuning', and you can peek at the number of threads vs what the pool maxes out at with 4 lines of code or so. However in .NET we have 'SynchronizationContexts' which result in the need for special dispatchers for things like a UI update (as most UI frameworks have their own context for handling UI).
> One of the benefits of this arrangement is if something is going slowly you can order the list of actors by biggest mailbox and you can see where your bottleneck is.
You could probably hack together something off of akka visualmailbox [0] as it shows how to grab metrics from 'inside' a mailbox. I did a toy port to .NET and while on that side I had to do a lot of 'configuration' and still need to create metrics collecting mailboxes for all the types (we don't have traits...) but it seemed to actually work not-bad.
`RefCell` is one "escape hatches".
Nothing can be assumed regarding data races if it happens to be a shared value of some sort modified via IPC or other kind of APIs across multiple processes accessing the same resource.
The library code down at the bottom layer might do that, depending on how the bindings to the APIs are implemented, but it won't be necessarily exposed to the code you are writing yourself.
For example, doing SQL statements isn't unsafe { }.
Yes, Rust prevents data races when several threads try to modify a memory location inside the same process.
This is just a special case of data races, which may take several forms.
If several processes, or even threads are accessing an external resource, like a database, each of them can issue UPDATE statements on the same record, and it is impossible to validate which value you will get back, unless it is done inside a proper transaction block.
Ensuring that a SQL data race doesn't happen, might be critical, e.g. several people to the same plane seat, yet there is nothing on the RefCell or not using unsafe {} that can enforce it.
I would advise to read the "Data Races and Race Conditions" chapter of Rustonomicon regarding what guarantees Rust actually provides, anything else is up to the programmer to take care they don't happen.
Pony is a completely new language based on the actor model where "threading" is a first-class operation and the type system is formally designed to make guarantees that make threads safer.
Pony really wants you to think differently about your problem and solution.
Much of Pony can be done with Arc/Mutex/&/move in Rust, but with special syntax, and it's that special syntax that kills the language frankly.
You can implement actors in Rust pretty easily. I've done it multiple times, including in a way that provides a pony-like nominal interface. It isn't as powerful, and it isn't as efficient, because frankly I'm not very good at this sort of thing and don't have time to work on it, but it's possible.
What pony gives is some syntactic sugar in some important places (the actor keyword, the be keyword) and a very strong runtime.
And saner defaults for type safety.
As much as I enjoy C++, the safety defaults due to backwards compatibility are mostly wrong.
EDIT: s/data races/race conditions
The borrow checker (in safe rust) always[0] prevents data races. It can't, however, prevent race conditions (but neither can pony do[1])
I typically think of a race condition an issue with concurrency where data might be modified by another thread which causes the program to return incorrect results.
Data race vs race condition:
A race condition happens when data/state is mutated because of the order in which concurrent processes occur. This could happen with threads, message passing, or many other ways.
I think this is distinct from deadlock which occurs when there is at least one circular dependency in the order when different "processes" must perform their operations.
As long as those data races originate from threads, it cannot prevent data races across processes, for example:
- Modifying the same file location
- Modifying the same table cell without transactions
- Talking to the same IO port
- Data sharing between CPU and GPU
From your experience with Pony, which escape hatches did you notice? How would a Pony programmer shoot themselves in the foot?
I still have no idea what actual Pony code looks like and I'm not sure where to click to find it.
I even clicked the link "learning pony." Of all the places to see at least a hello world, I'd like one there please.
When I am checking out a language for the first time, I like to at least see a sample of what I'll be committing to writing for the next few months.
go lang as a sample you can run on the home page! rust used to but you can still press playground to get it.
https://playground.ponylang.io/
...but the example isn't very enlightening.
Here's a networking example:
https://github.com/ponylang/ponyc/tree/master/examples/net
with the Main being in net.pony and the rest being the infrastructure.
And here's the start of an HTTP 1.1 app server (basically, just the HTTP parser at this point).
https://tutorial.ponylang.io/getting-started/hello-world.htm...
> Pony doesn’t care about filenames other than that they end in .pony. But it might matter to you! By giving files good names, it can be easier to find the code you’re looking for later
I cannot imagine anyone is going to be learning to code from the first time from this Hello World page. Although, the rest of the page is very much not for beginners, so maybe this was a fluke.
Often with new programming languages I wish they would present themselves in the form "Here's what you need to know about this language if you're already a professional developer" instead of an actual tedious tutorial.
$ echo "class Bar { }" > Foo.java
$ javac Foo.java
$ ls
Bar.class Foo.java
$If you don't specify any access modifier, it is the same as internal in .NET.
https://ponylang.zulipchat.com/#narrow/stream/189985-beginne...
Before jumping in, we suggest folks read our code of conduct here:
https://www.ponylang.io/community/code-of-conduct/
And social rules here:
https://www.ponylang.io/community/code-of-conduct/#social-ru...
To understand what the general expectations are within the community.
There was a big focus on both performance and stability/safety.
If there is anything you ever want to chat about from that podcast or anything pony related, feel free to find me on the Pony Zulip and DM me.
4 months ago https://news.ycombinator.com/item?id=24398469 (thanks threatofrain!)
2019 https://news.ycombinator.com/item?id=20370448
2018 https://news.ycombinator.com/item?id=18212633
2018 https://news.ycombinator.com/item?id=17195580
2018 https://news.ycombinator.com/item?id=16768706
2018 https://news.ycombinator.com/item?id=16264845
2017 https://news.ycombinator.com/item?id=15558051
2017 https://news.ycombinator.com/item?id=14999899
2017 https://news.ycombinator.com/item?id=14676505
2017 https://news.ycombinator.com/item?id=13846063
2016 https://news.ycombinator.com/item?id=12331458
2016 https://news.ycombinator.com/item?id=10902906
2015 https://news.ycombinator.com/item?id=9482483
related from 2019 https://news.ycombinator.com/item?id=19241427
The reference capability stuff can be a bit hard to wrap your mind around, but mostly in a good way, and the compiler errors are generally pretty helpful. If you come from an OO or procedural background, there will be a sharp learning curve. My experience of learning pony reference capabilities is roughly similar (in terms of forcing you to think differently about data sharing) to learning how to work with the rust borrow checker, despite them having different goals and operational models.
Isn't Pony object oriented?
Pony features classes and actors are like classes that can receive asynchronous messages.
However, unlike some OO, there's no inheritance. Unlike a lot of other OO there's a heavy emphasis on asynchronous messaging.
I wouldn't use "OO background" as an indicator of what might make Pony harder to learn. I think the key parts are experience with rigorous type systems and experience with concurrency. Without a background in those, your path to learning Pony will be harder than the path for someone who is already familiar with them.
https://github.com/ponylang/ponyc/tree/master/examples
You can find a number of pony library projects under the ponylang org on GitHub:
It's linked from the page, but we should make it more obvious.
This is the best page I have seen so far.
Sylvan is still involved but not in a coding kind of way. He's still involved in a variety of Pony decisions, discussions, etc. It's a rather different role than what he had in the beginning when he was the primary coder on it.
There was an uptick in contributions that were driven by Wallaroo Labs that involved improvements that were needed for Wallaroo, beyond that, it's all volunteers.
If you'd be interested in contributing, swing by the Zulip. We love to get new folks involved.
How does Pony relate to Joule? They seem to be on a continuum, what with Actors, object capabilities etc.
Pony needs to have something that shows how it's references are more/differently useful in a multi-actor program. I suspect that's a tall order.
Whether this is has a big impact on running systems remains to be seen, Erlang is very good at quickly collecting data.
Another thing that I would say Pony has that Erlang doesn't is an easy FFI mechanism. You can write NIFs for Erlang, but in my experience writing native code or wrapping C libraries has been much easier in Pony.
(Disclaimer, self-promotion):
It doesn't get easier than this: https://hexdocs.pm/zigler/Zig.html
(I'll be dropping direct c support in there in the next release)
I think the biggest hurdles when writing NIFs are:
* Interacting with Erlang terms from C/Rust/Zig. Admittedly both zigler and rustler help in this regard, by wrapping Erlang terms. Pony is able to expose raw pointers and structs to C, which I've felt easier to work with.
* Dealing with the Beam's preemptive scheduler. This isn't as big of a problem now with dirty schedulers but still a mismatch compared to normal Erlang code. Pony uses a cooperative scheduler everywhere, so you'll already be used to splitting long tasks in different steps by the time you need to use the FFI, which makes the transition easier.
https://www.youtube.com/watch?v=l848TOmI6LI
(this is an old api, there is a new api that makes the modes completely interchangeable: https://www.youtube.com/watch?v=kpRK9BC0-I8)
"There’s plenty to love about Pony, but more than anything else, what we love most is that Pony makes it easy to write fast, safe, efficient, highly concurrent programs."
In Erlang you can write safe highly concurrent programs, but you might struggle with fast and efficient. Of course, how much fast and efficient you need depends on what you are trying to do.
[0] https://www.ponylang.io/blog/2017/05/an-early-history-of-pon...
https://news.ycombinator.com/item?id=24398469
Has anything new happened to the language?
There has been nothing large or dramatic in the last 4 months. We are plugging away at improving ecosystem tools, the runtime, and a variety of other things; incremental improvement.
We spent money from our open collective account recently to purchase a couple Apple Silicon mac minis so we can get pony working on Apple Silicon machines. That's probably the biggest "outside the community" news that is coming.
And I'm probably stretching the definition of "outside the community" there.
I don't know exactly how it works, but at some point it writes things to some memory address. This is the point where the Rust compiler starts complaining about the situation if you try to use the bindings from something that can potentially be sent between threads. So you use some mutex, or a lock, and maybe wrap the whole thing in an atomic reference counted entity.
What would be the Pony way of solving a situation like this? It does say that it is possible to directly call C or C++ libraries.
I'd really appreciate if someone from the Pony community can elaborate a bit on such use cases.
Joe is probably the best person to talk to if you want to use ZMG as a reference point, you can find him on the Pony Zulip.
I mean, the answer can be 'just rewrite the underlying library in Pony'. Which would be fair enough.
"It depends on the specifics" would be the general answer.
Once you use FFI, there's no guarantee of memory safety as the C code has access to the entire address space of the process. There is no sandboxing of foreign code at this time.
If the code you are calling isn't thread safe and you run your Pony program with more than 1 scheduler thread (pony actors get run on 1 or more scheduler threads- generally 1 per scheduler thread per CPU) then "bad things canhappen". Becaue you are running unsafe code that can be accessed from multiple threads- but... well, maybe you can structure the Pony API so that you don't do that.
It really depends.
So from what I could gather from a quick glance through the tutorial, that would be to restrict the C code calls to a single actor, and have other actors call, I guess primarily through the async behaviors?
- Error handling. Uninspectable, untyped errors that still must be declared and caught is just a total waste of an error system. It's not actively harmful, since you can just use primitive unions instead, but it's still a waste. If it's a recoverable error, it should have types and be inspectable so you can recover from it; if it's unrecoverable, it shouldn't have to be caught or declared.
- The file-based package resolution scheme. It is an absolute blight on any language that has it.
- The lack of function overloading. I excuse this in Rust because Rust trait implementations have their own namespace, so nothing conflicts, but if there are two Pony traits with the same function with different types or a different arity, there doesn't appear to be a way to implement both on the same class.
Does Pony have such ambitions (I mean, the Pony team)?
My UC is an actor system in the browser and the ability to create a DSL created using the same language used in programming the actor system.
Pony is not dynamic but statically typed? Maybe it can be overcome by substituting actors since any actor can create any other actor?
Plus I'm interested in graphics. I googled but didn't see any examples of integrating either web or desktop graphics, like Skia.
As far as graphics go, I'd say Pony is quite early stage for such elaborate API's like Skia.
A partial function is where you can't compute a result based on the input.
If you need to know what went wrong (because more than one thing might have gone wrong and that needs to be communicated), you can use a union type for the result and match accordingly (as you would in Rust as an example).
If you have used it and have any ideas on what you'd like to see there, feel free to drop by Zulip!
"Makes the files package better" is something that has been on a "list of things to improve" for a long time.
open:[String]->FileDescrptor
ReadWrite restriction FileDescriptor to
read -> Item,
write[item] -> Void
ReadWriteOpen:[String]->ReadWrite
[aString] |->
aDescriptor <- Open.[aString],
Implementation ReadWrite
read |-> aDescriptor.read,
write[anItem] |-> aDescriptor.write[anItem]
fd <- ReadWriteOpen.["/etc/passwd"]
Now we have an Actor fd that can only do reads and writes onthe opened file.
Consequently, fd is just one thing in Actors as opposed to
kind of being two separated things in Pony.
Is the separation in Pony really a good idea?
I'm sure that's true in some domains, but I think they're pretty narrow. E.g., if I were building an internal API that managed money, I might buy that. But I think the vast bulk of software is essentially exploratory. We ship something minimal and see what we next need. In that context, learning what we really need to do is far more valuable than doing the not-quite-right thing with provable correctness.
Clearly, the sentence you quote is making an unqualified absolute statement, which are usually easy to pick apart and tear down. After reading the whole philosophy, I'm not really sure what they mean by "guarantee" and "pointless" here. I doubt they're saying that all programming in "unsafe" languages is "pointless."
I suspect they could just reword that sentence to say something more clear. E.g. what kinds of correctness they weigh most heavily. Does Pony protect me from all classes of all possible bugs? No? Because that is impossible? Agreed. Then which kinds of bugs?
Just before, it also says: "The Pony philosophy is neither “the-right-thing” nor “worse-is-better”. It is “get-stuff-done”." This is admitting some sort of balance is being struck here...
At least for humans, correctness and consistency are very difficult, and therefore very expensive to achieve except under narrow conditions. Mainly we don't bother. That's part of why dynamic languages like Python and Ruby are so popular. Correctness takes a back seat to convenience.
There's nothing wrong with Pony making these choices. Let a thousand flowers bloom. But when I look at a new language or tool, my first question is, "What kinds of problems might it be good for?" From this quote, plus a number of other things, it's pretty clear to me that they've chosen a relatively narrow domain, one I do very little work in.
"Pony is an open-source, object-oriented, actor-model, capabilities-secure, high-performance programming language"
Can you give an example of this? How is Pony "much" safer than Rust?
Given how easy it is to use c-ffi from Pony, it can be hard with existing tooling to feel confident in what your Pony code might do. Once you call out via C-FFI, all safety guarantees are off.
In this way, the c-ffi in Pony is as problematic for reasoning about safety as `unsafe` is in Rust.
Pony allows you to turn off FFI for all packages except for some that you allow but even then, FFI is an end around for the memory safety and capabilities security that the language otherwise provides.
Improving FFI tooling is an ongoing conversation amongst the core team.
https://www.ponylang.io/media/papers/a_prinicipled_design_of...
Capabilities describe both uniqueness and read-write ability. `iso` is fully unique and hence sendable, `trn` and `val` are write-unique (but `val` is also sendable since it isn't writable). The `tag` capability allows identity/message sending and is fully sendable
Recover blocks and automatic receiver recovery allow creation/usage of isolated data if only sendables are used from the external environment.
Only sendables can be included in messages.
Actors have `ref` access to themselves but `tag` access to other actors.
There is "Equivalence of ephemeral capabilities" for ephemeral indexing outside of iso and turn.
There is "Compatible capabilities with ephemeral modifiers" which shows ephemeral indexing does not matter for compatibility.
This is extended to types with "Compatible types" then "define the aliasing operator +, to give us the minimum compatible capability when a new alias to an object has been made" with "Aliasing" presenting the cases. There is "Unaliasing" and that's where I'm checking out.
Is there a general theory behind these kind of indexed judgement rules with case splitting?
* I have `iso^` because consumed the last reference to an `iso`, so this value can be sent anywhere or renamed
* I have `iso` because I've just evaluated a living reference to an `iso` object, so I can access fields and methods (with automatic receiver recovery) but can't move it anywhere
Unaliasing with `consume` is what produces `iso^`
I’ve seen this from other new languages lately. Please, study the Swift or TypeScript sites!
Most of the time the Promise/Future or async/await add mental overhead to code just for the sake of performance (because the monadic operations are only for sequential operations, it only make sense for applicative/parallel operations to use async/await if we don't care about performance). And with the Actor model, you don't need to use Promise or async 9 out of 10 times as you would in most languages without sacrifice performance.
If anyone from the Pony teams sees this, are actors in Pony conceptually similar to actors created with ray?
Turns out an easy google search answered my question as well... this post https://medium.com/distributed-computing-with-ray/ray-for-th... does say that ray uses the actor model, and ray is listed on The Actor Model wikipedia page as an Actor Model library.
A valid comparison would be to compare a Pony program to specific type of Rust program that involves considerable concurrent processing of data.
Pony's killer feature is a type system that allows programs to be written that are guaranteed to be safe from concurrency bugs. Like Rust, the Pony compiler is based on LLVM, and Pony programs compile to native code. Due to the Pony type system, the Pony GC is very efficient. Pony performance is pretty good, but as a language Pony is about much more than performance.
Most web apps generally need a lot of libraries to be "a good choice". Pony is lacking in libraries for "web development", so you'd need to do a lot of work that you wouldn't in other languages.
That's the case with most areas with Pony. You'll invest time in developing libraries you wouldn't in many other languages. In return, you get the nice list of "why pony" features, but, you are putting in a decent amount of work to get there.
That said if you release those libraries into the wild and get help maintaining them, the next time someone asks there will be less reason to say "you'll have to do some work".
It sounds like there is not (yet!) a package like Cowboy for Erlang for Pony? There is TCP server support but not HTTP and Rails-like stuff, eh?
If you don't mind me asking, what about using SQLite from Pony?
- https://github.com/ponylang/http_server
- https://github.com/theodus/jennet
Someone might have done SQLite for Pony, but I'm not aware of it. Writing network protocol stuff in Pony is usually pretty easy and the C-FFI is usually pretty easy which generally makes writing database connectivity (for at least happy path basics) fairly easy. (Add lots of caveats here).
If you'd like to talk more in-depth, swing by the Zulip and myself and other folks from the community can help out with answers.
template <class T> using shared_immutable = std::shared_ptr<const T>;
template <class T> using isolated = std::unique_ptr<T>;
That's basically how you'd guarantee that shared data is immutable and data passed along is held by only one thread.
Sure, sure, if you try, you can explicitly take out raw pointers out of them. You can also take an axe to your computer but, as they say, "just don't do that".