Hacker News new | past | comments | ask | show | jobs | submit login
Pony – High-Performance Safe Actor Programming (ponylang.io)
322 points by ibraheemdev on Jan 29, 2021 | hide | past | favorite | 152 comments



As a regular Pony user, and coding seriously in it for a few months, this is what I think;

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.


This sounds a lot like someone learning Rust. Both languages solve similar problems with very different solutions that come with complexity overhead.


Pony and Rusts' solutions to the concurrency problem are almost identical - forbid shared mutability through the type system. In what way are they very different?


I am not an expert in Rust's mechanics and only looked at it superficially, but I got the impression that shared references can't be guaranteed (to not exist in mutable ways) all the way through an object graph. I am not surprised if I am wrong about that. It wasn't the main deciding factor for me...

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


In safe Rust, all mutation must be either:

- 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.


What issues did you run into on the JVM with Java/Scala? I spent 5 years working up and down the entire akka stack so I'm curious to learn from your point of view. Also did you try akka typed or classic?


I'm not the parent poster but I'll have a go. Please feel free to correct me if I've got parts wrong, I'm hoping to learn something too.

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.


Also, serialization and object versioning challenges. I think I mentioned this in another comment but the lack of static typing in erlang/elixir tends to fence your composition model to where these sorts of problems are elided or explicitly handled in code.

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.


There are a couple of Rust actor frameworks, the most popular being actix [0] and bastion [1]

0: https://github.com/actix/actix 2: https://bastion.rs/


There are some escape hatches to get shared mutable reference. But in general those are impossible in rust.


Are they really "escape hatches"? Shared mutable state is as simple as `Rc::new(RefCell::new(x))`, or `Arc::new(Mutex::new(x))` for thread safety.


`Arc<Mutex<T>>` is not a shared mutable state. You cannot have two mutable reference at the same time. To mutate stuff inside the mutex, you got to hold the mutex, which guarantees that there are only one mutable reference at any given moment.

`RefCell` is one "escape hatches".


Worth noting that RefCell still enforces single-mutable-reference, it just does so at runtime. It lets you do things that could never be verified statically, but if you ever actually violate the condition, you'll get a panic (which is still better than memory unsafety).


Should be noted that this only works across threads.

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.


That would require getting into unsafe { } code and passing along a raw pointer, wouldn't it? Nothing regarding safety can be assumed about unsafe code at all


Nope, since when do you need to do that when using file IO or database connections?

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 { }.


What I'm not seeing is how the actual value contained inside a RefCell would get modified out from underneath your code without going through its checked methods, unless something has a raw pointer directly to its contents


Because you aren't looking at it from the context of data races anywhere on the application, and focusing on RefCell alone instead.

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.

https://doc.rust-lang.org/nomicon/races.html


Rust is essentially C++ with restrictions that make threads safer.

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.


While Pony is awesome, I think what you describe is a weakness and not a strength. If Pony were to move more of its hardest concepts to standard libraries, it would be a far simpler language, even if it maintained the actor/behavior concepts.

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.


The "move the hard parts to standard libraries" approach is exactly the approach Elixir and Erlang take. The OTP (the standard library for Erlang, and called seamlessly from Elixir) provides abstractions for almost every use case you'll ever need. It's super easy to write Elixir/Erlang without ever righting an explicit "receive" statement.


> Rust is essentially C++ with restrictions that make threads safer.

And saner defaults for type safety.

As much as I enjoy C++, the safety defaults due to backwards compatibility are mostly wrong.


At least with Rust, very few domains involve so much parallelism that one benefits from the overhead that the borrow checker adds to the development process. And many times the borrow checker is completely inadequate at preventing race conditions (frequently the case with distributed computing). Of course, Rust can recoup those losses elsewhere, by having better tooling or competing in domains where performance matters a lot, but I’ve not found the safety to be worthwhile in my domain.

EDIT: s/data races/race conditions


> And many times the borrow checker is completely inadequate at preventing data races (frequently the case with distributed computing).

The borrow checker (in safe rust) always[0] prevents data races. It can't, however, prevent race conditions (but neither can pony do[1])

[0] https://doc.rust-lang.org/nomicon/races.html

[1] https://www.ponylang.io/faq/#data-race


Right, I should have said "race conditions"; it hadn't occurred to me that the two weren't synonymous. My point wasn't that Pony does prevent race conditions, but rather that non-data-race race conditions are much more common in my domain (distributed computing) or really any domain where multiprocess architectures are common so I don't benefit much from the static guarantees that Rust affords.


Pony does not prevent race conditions. Two actors can wait forever for the other to send a message, deadlocking.


I didn’t say that Pony prevents race conditions. I agree that Pony (nor indeed any language) prevents race conditions.


I misread then. Though you did ironically just say that pony does prevent race conditions :P


grrrrr


Is deadlock considered a race condition?

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.


I think this might help:

Data race vs race condition:

https://blog.regehr.org/archives/490


That is helpful. I was thinking that a race condition would only include a data race, but this blog defined it broader, so that it would include deadlock.


Deadlock is one common symptom of a race condition.


You can have race conditions with message passing.


Yes, that makes sense.

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.


> The borrow checker (in safe rust) always[0] prevents data races.

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


In fairness to the OP, the link they cited defined "data race" explicitly and narrowly as race conditions on memory which I think makes all of your bullets out of scope, but also neuters the original claim pretty considerably.


Rust's borrow checker is not only for multi threading and memory safety. It allows someone designing an API for any kind of data structure to guarantee that the user is using it correctly, by making their code not compile when they are using it incorrectly (basically what any type system provides, but fancier). What you call borrow checker overhead, some would call fixing problems before they occur.


You’re right that there are other benefits of Rust’s type system beyond precluding data races, but even still, the overhead of Rust is too steep to make it a good fit for the kinds of applications I write.


What domain is that?


Distributed systems


^ seconded


And the Pony community has helped me out at every turn of confusion and getting my head twisted into the Pony model. Big kudos to them...


From my observation (as a curious non-user), all safe languages seem to include certain escape hatches. They guarantee safety, but if you know what you’re doing there’s always a %FEATURE% that opts you out and allows anything, including shooting yourself in the foot (getting a panic at runtime, etc.).

From your experience with Pony, which escape hatches did you notice? How would a Pony programmer shoot themselves in the foot?


C FFI.


I just spent 5 min going through the website reading about pony and its goals and use cases.

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.


Pony has a playground...

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://github.com/tmmcguire/shetland


I agree, but for anyone who's looking, here's the Hello World page.

https://tutorial.ponylang.io/getting-started/hello-world.htm...


Hmm

> 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.


That comment might be for programmers coming from languages where filenames do matter to the compiler, or languages like Java where the convention is so universally followed that many programmers don't know you can break it. Someone programming for the first time already "knows" that filenames don't matter, so it's probably not for them.


In Java, the “each public class is in its own file named after the class” rule is enforced by the compiler and there’s no switch to disable this behavior.


I guess that shows how uniformly the convention is followed that I wasn't aware of that! I remember finding a class in a PR that had been renamed without renaming the file, and we were surprised it compiled. It must not have been a public class.

    $ echo "class Bar { }" > Foo.java
    $ javac Foo.java
    $ ls
    Bar.class Foo.java
    $


That is not a public class.

If you don't specify any access modifier, it is the same as internal in .NET.


True, but many don't realize that package private and "friend" classes can share the same source file.


Good thing we had you here or there'd have been no world to say hello to.


If you are interested in learning more about Pony, please feel free to join our Zulip and ask away. It's the primary hub for the Pony community:

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.


Pony came up in the Corecursive podcast a few months ago, I enjoyed this episode on it: https://corecursive.com/055-unproven-with-sean-allen/

There was a big focus on both performance and stability/safety.


There was also a great explanation of why Pony and not Rust or Erlang, at least of that use case. I was the interviewer. "Pony: How I learned to stop worrying and embrace an unproven technology", a talk Sean gave about Pony is really good as well.

https://www.youtube.com/watch?v=GigBhej1gfI&t=1755s


Hiya. That's me.

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.



i hope you have a tool that pulls all those links together for you.


I think the tool is probably this one: https://hn.algolia.com/


dang simply remembers all the item?id=N. Which is easy because they're sequential. To make the list, mentally go through the numbers and paste in the urls that are relevant.


That's O(n) sir, not worth it.


I've only done hobby stuff with pony, but love the idea of it, and would love an opportunity to use it in production. I've found the community welcoming and helpful in my limited interactions. The standard library code is pretty easy to read, which somewhat mitigates the dearth of tutorials, howtos on the internet.

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.


> If you come from an OO or procedural background, there will be a sharp learning curve.

Isn't Pony object oriented?


It's in our tag line. But what OO means varies from person to person. There's no set definition of it.

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.


What sort of hobby projects have you found it a good match for out of interest?


what sort of hobby stuff do you do with pony? the language looks interesting but i've been unable to think of any compelling reasons to use it.


it's really frustrating seeing these programming languages websites where you have to click in a million place before you can even see what the code looks like


Is there an a page with more than a few lines of code to show what actual Pony programs look like?


There are a number of simple examples in the ponyc repo:

https://github.com/ponylang/ponyc/tree/master/examples

You can find a number of pony library projects under the ponylang org on GitHub:

https://github.com/ponylang/


I think you should consider showing some code on the front page, like D [0] or Ruby [1], or maybe have a "Try Pony" button that links to an online playground.

0: https://dlang.org/

1: https://www.ruby-lang.org/en/


We do have an online playground! You can try it here: https://playground.ponylang.io/

It's linked from the page, but we should make it more obvious.


Even when I knew it was there it took me a while to find it, so yeah, at the very least put it under its own header, first or second one.


Thanks, exactly what I needed to be confident that I should move on.


See also the Patterns part of the tutorial.

https://patterns.ponylang.io/


Do you have some examples of programming language websites you do like?


I really like Crystal's website [0]. It lets the code to the talking and has nice big buttons for "Install", "Learn", and "Try Online".

0: https://crystal-lang.org/


the few that come to mind : Elvish : https://elv.sh/ Flix : https://flix.dev/ Crystal : https://crystal-lang.org/ Nim : https://nim-lang.org/


https://flix.dev/

This is the best page I have seen so far.


Go is probably the best for this, it gives you a REPL on the homepage itself: https://golang.org/


And Rust has https://play.rust-lang.org/ - not a REPL, but a chance to kick the tyres before downloading anything.


Are Sylvan and Sebastian still involved with the language? The momemntum dropped the moment they left ...


Momentum dropped when Causality went out of business and it became a volunteer-driven project with no one working on it for pay.

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.


Does anyone remember the actor based programming language Joule [1]? It had a manual, and maybe some kind of private prototype, but never a public release as far as I know.

How does Pony relate to Joule? They seem to be on a continuum, what with Actors, object capabilities etc.

[1] http://www.cap-lore.com/Agorics/Library/joule.html


I recently read that a development branch of Java had Joule-based Capabilities, but that the SecurityManager model was ultimately preferred for simplicity. What a loss.


For anyone wondering "Why Pony?" - they have a section called just that:

https://www.ponylang.io/discover/#why-pony


There still isn't anything there that Erlang doesn't already have, excepting the theoretically-strong reference capabilities...which is what got me watching pony for a couple years.

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.


One thing that reference capabilities allow you to do is pass data between actors without copying. Erlang is very eager in copying almost everything you put in a message to a process (with the expection of big binaries, etc).

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.


> Another thing that I would say Pony has that Erlang doesn't is an easy FFI mechanism

(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)


Zigler looks interesting! I've given it a look before, would be very nice if it could support Erlang too, but I imagine Elixir macros are doing a lot of work for you.

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.


re: preemptive scheduler:

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)


I think that the first sentence on the why Pony page says it all

"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.


Totally subjective, but one thing I'd give Pony over Erlang is likable, consensual syntax. Erlang is weird, for no particular reason.


That didn’t answer the question I expected it to answer (“why Pony and not Brando, Gielgud, Nicholson, Streep…?”


You might be looking for "An Early History of Pony"[0] linked in the FAQ.

[0] https://www.ponylang.io/blog/2017/05/an-early-history-of-pon...


I am confused. Doing a search for "(Brando, Gielgud, Nicholson, Streep) programming language" turns up nothing.


I believe OP tried a joke, by naming famous actors - Pony being an actor programming language.


Prior discussion from 4 months ago.

https://news.ycombinator.com/item?id=24398469

Has anything new happened to the language?


pony core team member here.

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.


Do you need any help with Apple Silicon transition? I've purchased a new mac mini recently and looking for an open-source project to contribute.


Ok, so let's take an example. In a very hand-wavy way, the ZeroMQ messaging queue works without a central messaging broker or service, but purely as a library. In theory, you can implement ZMQ messaging primitives in your language of choice, but this is a lot of work, so it is usually used through bindings to libzmq C library.

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.


I don't know enough about ZMG to answer this. What little I know about it is mostly from a pure Pony version that a member of the core team worked on quite some time ago.

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.

https://ponylang.zulipchat.com/#


It's sort of a general question though. What happens if I use a C library from Pony that does or can potentially do unsafe things. I'm not sure I understand how such situations can be solved without some kind of a locking mechanism.

I mean, the answer can be 'just rewrite the underlying library in Pony'. Which would be fair enough.


That's an incredibly general question.

"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.


> maybe you can structure the Pony API so that you don't do that

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?


That would be the general approach if possible with c code that isn't thread safe. It's a matter of if that is possible with a given library (it's difficult for example with GTK).


Three things I definitely dislike about this language:

- 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.


I like Elixir, Erlang. There is Lumen, an OS initiative to bring the BEAM to the browser (WASM).

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.


There is also the Lunatic VM [0] which is an actor system built with Rust targeting WASM on the server.

0: https://github.com/bkolobara/lunatic


Since Pony uses LLVM, it should be possible to compile to WASM. Not sure if it's on the immediate roadmap of the Pony team. But I don't think the effort is as big as Lumen.

As far as graphics go, I'd say Pony is quite early stage for such elaborate API's like Skia.


I still am wondering how error handling should be done in pony, it only tells you an error has happend, not what has caused it or what kind of error happend.


`error` in Pony is for partial functions.

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).


Ah, i see. The files package does that apperently, correct? But cannot find it in the net package for example.


Occasional Pony committer here. You're right, and improving how errors are handled for socket operations has been in my mind for a while.

If you have used it and have any ideas on what you'd like to see there, feel free to drop by Zulip!


The files package in general uses `error` and isn't very helpful in letting you know what went wrong.

"Makes the files package better" is something that has been on a "list of things to improve" for a long time.


I tried using Pony for 2019's Advent of Code, and I ran into this problem. I ended up writing a whole blog post about the way that I tackled error-handling, which I don't think is the solution (especially since the Pony team is looking at new ways of expressing error-handling) but was sufficient for the programs I was writing: https://journal.infinitenegativeutility.com/pony-errors-and-...


Consider the following example:

    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 on

the 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?


If you are going to create a new language in 2021 please make it use scoped + name-spaced module imports. Everything becomes difficult if you can't even know which variables are used where and what it is / originate from. Global variables are evil.


Huh. This is something I don't think I agree with: "Incorrectness is simply not allowed. It’s pointless to try to get stuff done if you can’t guarantee the result is correct."

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.


For reference, you're pulling that quote from https://www.ponylang.io/discover/#the-pony-philosophy

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...


Sure, and as I said, I think their get-stuff-done philosophy must apply in pretty narrow contexts if they prioritize correctness that strongly.

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.


I never saw Pony described as high performance. Now I'm interested


That's how I always seen it described. Not high performance as in low level signal processing, but as in server software. I would think of it like an alternative to Go (and Erlang), but with more focus on performance, safety and more functional and ruby-like rather than C-like. What they are sacrificing is simplicity, and perhaps ease-of-reasoning.


The tag line that has carried over on all the website versions is:

"Pony is an open-source, object-oriented, actor-model, capabilities-secure, high-performance programming language"


Pony 1) isn't interpreted and 2) tries to avoid copying data between actors (think threads).


Well, in the very first benchmarks it was the fastest of all on multicore machines. Faster than C++ with pstl/openmp. Lockfree/non-blocking stdlib has its advantages. It's also safe, much safer than rust.


Not saying that Rust is fault-proof but that's a pretty bold claim. How do you back it?


> It's also safe, much safer than rust.

Can you give an example of this? How is Pony "much" safer than Rust?


As a member of the pony core team, I'm not comfortable saying Pony is safer than Rust. The approach of the two is different; each with their own trade-offs.

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.


Is not the Pony c-ffi, while allowing theoretically the same unsafety, more uncontrolled than rust's "unsafe"? As in the latter case, as I have understood it, you get all the normal rust type checking, except for some pointer ownership exceptions. I have not explored either of the languages too the extent that I needed to use either.


Anyone know the judgement rules behind the interesting parts of Pony's type system? I'm about to crack the dissertation to see what's up but would love a concise description.

https://www.ponylang.io/media/papers/a_prinicipled_design_of...


The interesting parts:

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.


Could dependent types model capabilities? Would never be as ergonomic as Pony, but I'm wondering about expressive power of dependent types compared to indexed judgement rules or linear judgement rules.


I took a stab at it, seems everything is indexed by capabilities which makes sense.

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?


Basically unaliasing distinguishes the cases:

* 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 spent a few minutes on the site trying to find a code sample without launching into the playground... Even the “Getting Started” and “Tutorial” pages just repeat the features from the front page with zero code.

I’ve seen this from other new languages lately. Please, study the Swift or TypeScript sites!


Reminds me of the (newer) Motoko for writing smart contracts https://sdk.dfinity.org/docs/language-guide/motoko.html


First Erlang/Elixir, then Ruby 3, now Python. It's great that people are picking up the good idea once again.

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.


Pony actors look similar to the python ray package actor api.

If anyone from the Pony teams sees this, are actors in Pony conceptually similar to actors created with ray?


I'm not familiar with the python ray package. If you are familiar with actors as they exist in erlang and elixir, there's a comparison of those to Pony that might help your understanding.

https://www.youtube.com/watch?v=_0m0_qtfzLs


Thanks!

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.


So eager if one'd have done a benchmark with rust


I don't really think that a comparison to Rust is all that useful. Rust and Pony are really intended for different purposes. Rust is systems programming language. Pony is an application programming language that provides an actor model for concurrent programming and a runtime to support the actors and perform garbage collection.

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.


Would Pony be a good choice for writing a backend for a web app? What's it like talking to a DB?


That really depends on your idea of what makes something a good choice.

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".


Thanks for taking the time to reply. (FWIW, I'm in the market for a new language. I've been mostly using Python in the past, but now I want something like Erlang or Pony, or maybe Nim, or Zig or D or OCaml or ...?)

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?


There's an http server and a small "sinatra" like web framework.

- 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.

https://ponylang.zulipchat.com/


Oh cool! That might be all I need. Thanks again.


How Pony compares to ada?


How about, in C++, doing:

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".




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact