Document not found (404)
+This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..f8414f0 --- /dev/null +++ b/404.html @@ -0,0 +1,3 @@ + +
Current state, not comprehensively listed, but items of some noteworthiness:
+This is not a promise of delivering any feature at a certain point in time. It's to give you a rough idea of what the future might hold.
+We believe the existence of a fault tolerant distributed programming environment with orthogonal persistence will make the world a better place. Acton was created to deliver on that belief.
+We want a programming environment that offers persistence so that programmers do not need to spend time writing code to save program state to disk or a database (which in turn writes to disk, typically). There are few such languages in existence and even fewer, if any, in practical use. This is especially important as handling state persistence is particularly hard when writing distributed systems or working in cloud environments. The programmer should not need to spend much time dealing with state persistence.
+We want to write small programs that can run together to form a large system. It needs to scale beyond the bounds of a single computer and we don't want the programmer to spend any extra effort to achieve this.
+We want the system as a whole to be robust. It must tolerate failures to the hardware compute nodes used to run the system without loss of data or function. It should also help the programmer to write correct software without bugs. It's not practical to avoid bugs entirely but when we have a chance to detect and present them early, that is better than later.
+We want a reasonably fast system. Performance is not the highest priority but one shouldn't have to needlessly buy newer or more hardware.
+We want to lower the barrier of entry and enable people to build the robust systems that modern society needs and relies on.
+Acton adopts the actor model and combines it with a simple to use syntax and a powerful type system. Many good things emanate from the combination of the actor model and a good type system.
+ +In traditional languages, programs use shared memory to allow multiple threads or multiple processes to share state or communicate with each other. Shared memory involves locks to avoid race conditions. Checkpointing the state of a program is hard because it is hard to figure out what parts of the memory you need to checkpoint. It's all like a big hairy spaghetti ball and the safe choice is to checkpoint the entire memory of the program. This could work for small programs but we want to write large distributed programs! The key to efficiently checkpointing a program is to be able to work on smaller parts of the program and in order to do that, we need to get a grip on those smaller parts. That is hard with shared memory. There are papers on this topic with various techniques applied in attempts to realize barriers and snapshot chunks of memory but it is hard to make it work well in practice. In contrast, with actors, each actor has its own private memory which makes it possible to checkpoint the state of individual actors. Further, actors communicate with each other through messages. With our own database that implements queues inside the database, we can both read and modify database cells as well as reading, consuming or putting new items on the queues in the same transaction! This makes it possible to deal with actor checkpoints in a transactionally safe way. Many great things emanate from the actor model!
+Actors do not fundamentally resolve the problem of snapshotting since it is still possible to construct a single giant actor that consumes 100GB of RAM and snapshotting that won't be fast. However, the actor model does encourage the use of many smaller actors for concurrency as well as logically splitting up your program in manageable units. In practice, it typically reduces the size and time taken to checkpoint by orders of magnitudes compared to a large traditional program. It brings persistence-through-checkpointing from theoretically feasible to practically viable.
+Checkpointing needs to happen when there are externally visible effects. The actor models makes this trivial to determine since anything that is externally visible is going to be a message to another actor. To avoid explicit state checkpointing calls, synchronization primitives or explicit IPC calls that would be forced upon the application developer, we employ magic advanced techniques in the compiler, made possible by its inherent knowledge of the language. By analyzing the code of the program we can determine suitable locations for inserting checkpointing, and this is in fact what the Acton compiler does. It splits up a program into continuations that are executed by the Acton run time system. After a continuation has been executed, its result, including the actor state, any outgoing messages to other actors as well as the consumption of the incoming message that triggered this continuation to run, is committed atomically in one transaction to the database. All this is possible thanks to the language, compiler, run time system and database working together as one. It is only possible with a new language and that is why we wrote Acton.
With Acton, an application developer simply writes single threaded Acton code and relegates the complex tasks of ensuring state consistency, persistence, fault tolerance and load balancing to the compiler, database and run time system.
+We hope you enjoy Acton!
+Acton is still relatively young and still has some way to go to fully deliver on the vision. Read more about the current state and outlook.
+We have worked on Acton for years. The first iteration of Acton was essentially a proof of concept where we used Python as the run time for the language with a heavily modified Cassandra database that featured transaction and communication queues for safely storing state. It was tied together by an early version of the Acton compiler, written in Haskell, that took Acton source code as input and produced a Python program which a standard Python interpreter could execute. It would wrap Acton actors in state preserving code that would commit the programs state to the database. This prototype was used to prove the foundational concepts.
+Since then we have implemented a new high performance run time system and a new distributed database for backend. Acton code is now compiled to C and then to machine code. The runtime system also features a new lock-free distributed database, written from scratch in C for Acton, making use of efficient optimistic concurrency transaction validation, gossip, bulk updates, integrated communication queues, and several other optimizations that make it fast. The database offers a low level C API for simple but very fast and low latency queries; suitable for building a language run time system on top of! The new run time system in turn is designed to work well together with the database.
+The run time system works well standalone too, you don't have to use it together with the distributed database backend. You can use it to write "normal" programs and with native code, it is easy to make small independent programs that are easy to install since there is no need to first install an interpreter.
+ + +Write programs that seamlessly run as a distributed system over an entire data center or region. All without a single line of RPC code.
+Acton automatically persists the state of your application (orthogonal persistence) to a built-in distributed backend. No need to use a database or message broker ever again. 0 lines of persistence code.
+Built-in redundancy; Acton's transactional, high performance distributed RTS can seamlessly resume application state after hardware failures
+Never stop for an upgrade; Live upgrade your running application through compiler-supported code and data migration
+Acton programs, and the actor model, work well from simple script style applications on a single machine up to large distributed systems across a Data Center. Run at your scale.
+Static and strongly typed, Acton is safe yet simple to use with low overhead thanks to powerful type inferencing. Being a compiled language, backed by a high performance distributed run time system, Acton is fast.
+Acton is a general purpose programming language, designed to be useful for a wide range of applications, from desktop applications to embedded and distributed systems. In a first approximation Acton can be described as a seamless addition of a powerful new construct to an existing language: Acton adds actors to Python. Our take on the actor model allows developers to write highly scalable and fault tolerant code, without needing to worry about explicit state checkpointing, synchronization primitives or giving up consistency.
+Acton is a compiled language, offering the speed of C but with a considerably simpler programming model. There is no explicit memory management, instead relying on garbage collection.
+Acton is statically typed with an expressive type language and type inference. Type inferrence means you don't have to explicitly declare types of every variable but that the compiler will infer the type and performs its checks accordingly. We can have the benefits of type safety without the extra overhead involved in declaring types.
+The Acton Run Time System (RTS) offers a distributed mode of operation allowing multiple computers to participate in running one logical Acton system. Actors can migrate between compute nodes and load balance the application workload. The RTS offers exactly once message delivery guarantees and strong serial state consistency without sacrificing performance. By automatically checkpointing actor states and inter-actor messages to a integrated distributed database and messaging queues, failure of individual actors or compute nodes can be transparently recovered from by the runtime system. There is no need for explicit state checkpointing or use of synchronization primitives between actors. Your system can run forever!
+Acton is a work in progress and still has some way to go to fully deliver on the vision. We want to sell you on the ideas and concepts that we set out to deliver, so that you may be as excited about Acton as we are :)
+Read more about the motivation behind Acton and why we thought a new language was the best avenue to achieve our goals, also covering some of the early history and learn about where we are currently at and what the future might hold.
+Check out the installation section for how to get Acton!
+Read an introduction to using Acton in the learn Acton section.
+ + +One of the best ways of helping Acton development is to start using Acton for writing your programs and reporting on any bugs found or the overall experience.
+You are of course more than welcome to make direct contributions too, e.g. writing documentation, designing this web page, hacking on the actonc compiler or expanding the standard library.
+Come help us make Acton better!
+If you want to mess around with Acton itself, like hack on the compiler or add to the standard library of modules you will need to build the Acton system from source.
+git clone git@github.com:actonlang/acton.git
+
+Install the build time dependencies.
+apt install bzip2 curl g++ haskell-stack make procps zlib1g-dev
+
+Debian 12 (bookworm) or Ubuntu 23.04 or newer are required to get a fresh enough version of stack that works with GHC 9. On older distributions, stack needs to be upgraded first.
+stack upgrade
+export PATH=~/.local/bin:$PATH
+
+brew install haskell-stack
+
+Run make
in the project root:
make -j
+
+-j
is for parallel make using as many CPU cores as available on your machine.
make test
+
+
+
+ There are pre-built binary release tar balls available for download for Linux +and Mac OS X in case the above package formats are not suitable. Download a +tar ball from the Release page. +Pick the latest stable versioned release.
+In case you are looking to live on the bleeding edge or have been asked by a
+developer (in case you ran into a bug) you can pick tip
, which is built
+directly from the main
branch.
Extract the Acton tar ball:
+$ tar jxvf acton-*
+
+You will want to include the acton/bin
directory in your PATH
so you can use
+actonc
.
Acton is available for Linux and Mac OS X.
+The platforms listed here are for developing with Acton. The final binary executable produced from compiling an .act
can run on other platforms, for example Ubuntu 18.04 and can be made to run on even older systems through static linking, see Acton by Example: compilation
The APT configuration is for bullseye
but the Acton package is forward compatible and works just fine on bookworm as well as similarly modern Ubuntu releases.
wget -q -O - https://apt.acton-lang.io/acton.gpg | sudo apt-key add -
+echo "deb [arch=amd64] http://apt.acton-lang.io/ bullseye main" | sudo tee /etc/apt/sources.list.d/acton.list
+sudo apt-get update
+sudo apt-get install -qy acton
+
+brew install actonlang/acton/acton
+
+There are pre-built binary release tar balls available for download for Linux +and Mac OS X in case the above package formats are not suitable. See the guide, +installing Acton from a release tar ball.
+See building from source.
+ + +This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +Actors is a key concept in Acton. Each actor is a small sequential process with its own private state. Actors communicate with each other through messages, in practice by calling methods on other actors or reading their attributes.
+Source:
+# An actor definition
+actor Act(name):
+
+ # Top level code in an actor runs when initializing an actor instance, like
+ # __init__() in Python.
+ print("Starting up actor " + name)
+
+ def hello():
+ # We can directly access actor arguments, like `name`
+ print("Hello world from " + name)
+
+actor main(env):
+ # Create an actor instance a of Act
+ a = Act("FOO")
+ # Call the actor method hello
+ await async a.hello()
+
+ env.exit(0)
+
+Compile and run:
+actonc actors.act
+./actors
+
+Output:
+No dependencies found, building project
+Building project in /tmp/tmp_nwgl0ik/example
+ Compiling example.act for release
+ Finished compilation in 0.015 s
+ Final compilation step
+ Finished final compilation step in 0.437 s
+Starting up actor FOO
+Hello world from FOO
+
+
+ As actors are sequential programs and can only do one thing at a time, it is important not to spend time waiting in a blocking fashion. Acton leverages asynchronous style programming to allow actors to react and run only when necessary. Async is at the core of Acton!
+A method is called asynchronously when the return value is not used.
+Source:
+
+def nsieve(n: int):
+ """Sieve of Erathostenes to find primes up to n
+ """
+ count = 0
+ flags = [True] * n
+ for i in range(2, n, 1):
+ if flags[i]:
+ count += 1
+ for j in range(i, n, i):
+ flags[j] = False
+ return count
+
+actor Simon(idx):
+ def say(msg, n):
+ # Simon likes to compute primes and will tell you how many there are under a given number
+ count = nsieve(n)
+ print("Simon%d says: %s.... oh and there are %d primes under %d" % (idx, msg, count, n))
+
+actor main(env):
+ s1 = Simon(1)
+ s2 = Simon(2)
+
+ s1.say("foo", 1000000)
+ s2.say("bar", 5)
+
+ def exit():
+ env.exit(0)
+ after 0.2: exit()
+
+Compile and run:
+actonc async.act
+
+Output:
+Simon2 says: bar.... oh and there are 2 primes under 5
+Simon1 says: foo.... oh and there are 78498 primes under 1000000
+
+A method call like s1.say("foo", 100000)
does not use the return value of and is thus called asynchronously. We ask s1
to compute primes under 1000000 while s2
only gets to compute primes up to 5
which will invariably run faster. Thus, s2
despite being called after s1
, will print out its result before s1
. The s1
and s2
actors are called asynchronously and are executed concurrently and in parallel.
The call flow can be illustrated like this. We can see how main
asynchronously calls s1
and s2
that will be scheduled to run concurrently. The run time system (RTS) will run s1.say()
and s2.say()
in parallel if there are 2 worker threads available. Per default, there are as many worker threads as available CPU threads.
In addition we see how the call to after 2
schedules the main
actor to run again after 2 seconds, specifically it will run the main.exit()
method, which in turn exists the whole program.
Actors typically contain some private state. We define variable attributes at the top level in the actor using the var
keyword and can then access them from any method within the local actor. Note how self.
is not needed. Private variables are not visible from other actors.
Source:
+actor Act():
+ var something = 40 # private actor variable attribute
+ fixed = 1234 # public constant
+
+ def hello():
+ # We can access local actor variable attributes directly, no need for
+ # self.something or similar
+ something += 2
+ print("Hello, I'm Act & value of 'something' is: " + str(something))
+
+actor main(env):
+ actor1 = Act()
+ await async actor1.hello()
+ print("Externally visible constant: ", actor1.fixed)
+ # This would give an error, try uncommenting it
+ # print(actor1.something)
+
+ env.exit(0)
+
+Compile and run:
+actonc attrs.act
+
+Output:
+Hello, I'm Act & value of 'something' is: 42
+Externally visible constant: 1234
+
+Without the var
keyword, an actor attribute is a constant. As constants are not mutable, it is safe to make it visible to other actors and it can be accessed like an attribute on an object.
It is possible to run special code when an actor is about to be garbage collected. This should not be performed by normal actors, this is really only for I/O actors that interact with the environment and might need to also clean up in the environment when the actor is being garbage collected.
+Define an actor action called __cleanup__
and the run time system will automatically install it to be run at garbage collection time. There is no hard guarantee when the __cleanup__
function will be called and it typically takes a couple of collection rounds to run all finalizers.
Source:
+actor Foo():
+ action def __cleanup__():
+ print("Cleaning up after myself...")
+
+actor main(env):
+ # create a bunch of actors and do not assign reference, so they should be collected
+ for i in range(20):
+ Foo()
+
+ # perform some busy work to eventually trigger the GC and thus finalization
+ # & schedule __cleanup__
+ a = 1
+ for i in range(99999):
+ a += i
+
+ # Delay exit a little bit to let RTS workers pick up the asynchronously
+ # scheduled __cleanup__ actions to run for the unreachable instances of the
+ # Foo actor
+ def _stop():
+ env.exit(0)
+ after 0.1: _stop()
+
+Output:
+Cleaning up after myself...
+Cleaning up after myself...
+Cleaning up after myself...
+Cleaning up after myself...
+Cleaning up after myself...
+Cleaning up after myself...
+Cleaning up after myself...
+Cleaning up after myself...
+Cleaning up after myself...
+Cleaning up after myself...
+Cleaning up after myself...
+Cleaning up after myself...
+Cleaning up after myself...
+Cleaning up after myself...
+Cleaning up after myself...
+Cleaning up after myself...
+Cleaning up after myself...
+Cleaning up after myself...
+Cleaning up after myself...
+Cleaning up after myself...
+
+
+ Multiple actors run concurrently. In this example we can see how the two actors Foo and Bar run concurrently. The main actor is also running concurrently, although it doesn't do anything beyond creating the Foo and Bar actors and exiting after some time.
+Source:
+actor Counter(name):
+ var counter = 0
+
+ def periodic():
+ print("I am " + name + " and I have counted to " + str(counter))
+ counter += 1
+
+ # 'after 1' tells the run time system to schedule the specified
+ # function, in this case periodic(), i.e. ourselves, after 1 second
+ after 1: periodic()
+
+ # First invocation of periodic()
+ periodic()
+
+
+actor main(env):
+ # Create two instances of the Counter actor, each with a unique name
+ foo = Counter("Foo")
+ bar = Counter("Bar")
+
+ def exit():
+ env.exit(0)
+
+ # exit the whole program after 10 seconds
+ after 10: exit()
+
+Compile and run:
+actonc concurrency.act
+./concurrency
+
+Output:
+I am Foo and I have counted to 0
+I am Bar and I have counted to 0
+I am Foo and I have counted to 1
+I am Bar and I have counted to 1
+I am Foo and I have counted to 2
+I am Bar and I have counted to 2
+I am Foo and I have counted to 3
+I am Bar and I have counted to 3
+I am Foo and I have counted to 4
+I am Bar and I have counted to 4
+I am Bar and I have counted to 5
+I am Foo and I have counted to 5
+I am Bar and I have counted to 6
+I am Foo and I have counted to 6
+I am Bar and I have counted to 7
+I am Foo and I have counted to 7
+I am Foo and I have counted to 8
+I am Bar and I have counted to 8
+I am Foo and I have counted to 9
+I am Bar and I have counted to 9
+
+
+ The main function in most imperative and functional programming languages start at the top and when they reach the end of the function, the whole program exits. Actors exist as long as another actor has a reference to it and will idle, passively waiting for the next method call. Actors without references will be garbage collected. The root actor of a program will always exist even without other references.
+This means that a simple program like this modified helloworld (the env.exit()
call has been removed) will run indefinitely. You need to deliberately tell the run time system to stop the actor world and exit, via env.exit()
, in order to exit the program.
Source:
+actor main(env):
+ print("Hello world!")
+
+Compile and run:
+actonc noexit.act
+
+Output:
+$ ./noexit
+<you will never get your prompt back>
+
+
+ Like C has a main() function, Acton has a root actor. To compile a binary executable, there must be a root actor. Per default, if a source (.act
) file contains an actor named main
, it will be used as the root actor but it can also be specified using the --root
argument. While the convention is to call the root actor main
, you are free to name it anything.
Given this Acton program:
+actor main(env):
+ print("Hello World!")
+ env.exit(0)
+
+The following actonc commands will all produce the same output.
+actonc hello.act
+actonc hello.act --root main
+actonc hello.act --root hello.main
+
+The first invocation relies on the default rule of using an actor called main
. The second invocation explicitly specifies that we want to use main
as the root actor while the third uses a qualified name which includes both the actor name (main
) as well as the module name (hello
). Using qualified names can be particularly useful when building executable binaries in projects.
A normal Acton program consists of many actors that are structured in a hierarchical tree. The root actor is at the root of the tree and is responsible for starting all other actors directly or indirectly. The Acton Run Time System (RTS) will bootstrap the root actor.
+ +Any executable Acton program must have a root actor defined. Acton libraries (that are included in an another Acton program), do not need a root actor.
+ +While async is good for performance it makes it somewhat convoluted, forcing use of callbacks, to just return a value. Acton makes it possible to call other actors in a synchronous fashion for ease of use.
+A method is called synchronously when the return value is used.
+Source:
+import acton.rts
+
+actor DeepT():
+ def compute():
+ # some heavy computation going on
+ acton.rts.sleep(1)
+ return 42
+
+actor main(env):
+ d1 = DeepT()
+
+ answer = d1.compute()
+ print("The answer is", answer)
+
+ env.exit(0)
+
+Compile and run:
+actonc sync.act
+
+Output:
+The answer is 42
+
+The call flow can be illustrated like this. We can see how the execution of main
is suspended while it is waiting for the return value from actor d1
.
While synchronous is bad because we block waiting for someone else, we are only ever going to wait for another actor to run its method. There is never any wait for I/O or other indefinite waiting, only blocking wait for computation within the Acton system. This is achieved by the lack of blocking calls for I/O, thus even if there is a chain of actors waiting for each other
+ +Inheritance is a way of creating a new class from an existing class. The new class is called the derived class and the existing class from which we inherit is called the base class. The derived class inherits all the attributes and methods of the base class. The derived class can extend the base class by adding more attributes or methods. It is also possible to override methods to create more specific functionality.
+We add the area()
to Circle
to get the area and realize that the unlike the diameter, area is common for all gemetric shapes. Thus we create a base class Shape
that defines the area()
method, but does not implement it since there is no generic way to compute the area for all shapes. Each concrete class, like Circle
and Square
, should implement area()
.
Source:
+class Shape(object):
+ def area(self) -> float:
+ raise NotImplementedError("This method should be overridden by subclasses")
+
+
+class Circle(Shape):
+ radius: float
+
+ def __init__(self, radius):
+ self.radius = radius
+
+ def diameter(self):
+ return self.radius * 2
+
+ def area(self):
+ return 3.14 * self.radius ** 2
+
+
+class Square(Shape):
+ def __init__(self, side: float):
+ self.side = side
+
+ def area(self) -> float:
+ return self.side ** 2
+
+
+actor main(env):
+ circle = Circle(3.14)
+ square = Square(3.14)
+ print(circle.area() + square.area())
+ env.exit(0)
+
+Inheritance is one of the primary methods, if not the primary method, of structuring programs in the object oriented paradigm.
+Shape
is an abstract class because it has no __init__
method.
Acton supports the object-oriented paradigm, which means that it provides features to create classes and objects, which are instances of classes. Classes are a fundamental concept in an object-oriented world and they allow programmers to create their own data types with their own attributes and methods.
+A class is defined using the class
keyword followed by the name of the class. The convention is to use CamelCase for class names.
class Circle(object):
+ radius: float
+
+ def __init__(self, radius):
+ self.radius = radius
+
+ def diameter(self):
+ return self.radius * 2
+
+Attributes are variables that hold data for an object of a particular class and methods are functions that operate on that data. In the above example, radius
is an attribute of the Circle
class and diameter()
is a method that returns the diameter.
Class methods must have self
as the first argument, which refers to the object instance of the class that the method is called on.
A Class is like a blueprint and an object is an instance of such a blueprint. To create an object, or "instantiate", we use the "blueprint" (class), like so:
+circle = Circle(3.14)
+
+Here we create the object circle
from the class Circle
, passing the parameter 3.14
which will be used to set the radius
attribute of the object.
print(circle.diameter())
+
+And here we print the diameter of the circle by calling the .diameter()
method.
Acton is a compiled language and as such, outputs binary executables. It is possible to influence the compilation process in various ways.
+The default target is somewhat conservative to ensure a reasonable amount of compatibility. On Linux, the default target is GNU Libc version 2.27 which makes it possible to run Acton programs on Ubuntu 18.04 and similar old operating systems. Similarly, a generic x86_64 CPU is assumed which means that newer extra CPU instruction sets are not used.
+To compile an executable optimized for the local computer, use --target native
. In many cases it can lead to a significant faster program, often running 30% to 100% faster.
On Linux, executable programs can be statically linked using the Musl C library, which maximizes portability as there are no runtime dependencies at all.
+To compile an executable optimized for portability using musl on x86_64, use --target x86_64-linux-musl
.
A default compiled program is dynamically linked with GNU libc & friends
+$ actonc helloworld.act
+Building file helloworld.act
+ Compiling helloworld.act for release
+ Finished compilation in 0.013 s
+ Final compilation step
+ Finished final compilation step in 0.224 s
+$ ldd helloworld
+ linux-vdso.so.1 (0x00007fff2975b000)
+ libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f11f472a000)
+ libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f11f4725000)
+ libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f11f4544000)
+ libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f11f453f000)
+ /lib64/ld-linux-x86-64.so.2 (0x00007f11f4827000)
+$
+
+A program linked statically towards Musl has no run time dependencies:
+$ actonc helloworld.act --target x86_64-linux-musl
+Building file helloworld.act
+ Compiling helloworld.act for release
+ Finished compilation in 0.013 s
+ Final compilation step
+ Finished final compilation step in 0.224 s
+$ ldd helloworld
+ not a dynamic executable
+$
+
+Although untested, static linking with musl should work on other CPU architectures.
+MacOS does not support static compilation.
+Acton supports cross-compilation, which means that it is possible to run develop on one computer, say a Linux computer with an x86-64 CPU but build an executable binary that can run on a MacOS computer.
+Here's such an example. We can see how per default, the output is an ELF binary for x86-64. By setting the --target
argument, actonc
will instead produce an executable for a Mac.
$ actonc --quiet helloworld.act
+$ file helloworld
+helloworld: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.0.0, with debug_info, not stripped
+$ actonc --quiet helloworld.act --target x86_64-macos-none
+$ file helloworld
+helloworld: Mach-O 64-bit x86_64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
+
+It is not only possible to compile for other operating systems, but also for other CPU architectures. For example, use --target aarch64-macos-any
to produce a binary executable for an Apple M1/M2 CPU.
Acton ships with prebuilt libraries for the local platforms default target, i.e. if you install Acton on a x86-64 Linux machine, it will have libraries prebuilt for x86_64-linux-gnu.2.27. The default target uses these prebuilt libraries which results in a fast build:
+$ actonc helloworld.act
+Building file helloworld.act
+ Compiling helloworld.act for release
+ Finished compilation in 0.013 s
+ Final compilation step
+ Finished final compilation step in 0.224 s
+$
+
+When targeting something that is not the default target, the entire Acton system, including builtins, the run time system, the standard library and external library dependencies is built from source and can take a significant amount of time. The build process is highly parallelized and cached. For example, on an AMD 5950X with 16 cores / 32 threads, it takes around 7 seconds to do a complete rebuild for a small Acton program as can be seen here:
+$ actonc helloworld.act --target aarch64-macos-none
+Building file helloworld.act
+ Compiling helloworld.act for release
+ Finished compilation in 0.012 s
+ Final compilation step
+ Finished final compilation step in 6.847 s
+$
+
+In an Acton project, there is a build cache, is is stored in a directory called build-cache
in the project directory. The cache is always used for the project local files. If a non-default --target
is being used, the built output of the Acton system is also stored in the cache, which means that it is only the first time around that it is slow. Any subsequent build is going to use the cache and run very fast. Like in this example, where the first invocation takes 6.120 seconds and the second one runs in 0.068 seconds.
$ actonc new hello
+Created project hello
+Enter your new project directory with:
+ cd hello
+Compile:
+ actonc build
+Run:
+ ./out/bin/hello
+
+Initialized empty Git repository in /home/kll/hello/.git/
+$ cd hello/
+$ actonc build --target native
+Building project in /home/kll/hello
+ Compiling hello.act for release
+ Finished compilation in 0.012 s
+ Final compilation step
+ Finished final compilation step in 6.120 s
+$ actonc build --target native
+Building project in /home/kll/hello
+ Compiling hello.act for release
+ Already up to date, in 0.000 s
+ Final compilation step
+ Finished final compilation step in 0.068 s
+$
+
+When compiling standalone .act files, there is no project and thus no persistent cache, so using a custom --target
will always incur a penalty.
A crucial part in any imperative program is to control the flow of execution. Acton supports a number of different constructs for this:
+actors
if
/ else
for
while
after
/ sleep
The basic control flow of most programming languages involve a starting point, like a main function, which is run from top to bottom, after which the program implicitly terminates. The basic objective is to feed instructions to the CPU and this goal remains through increasing levels of abstractions. Acton is different. Once created, an actor will simply remain indefinitely, waiting for incoming messages in the form of actor method calls. See Actor Lifetime.
+Actors in an Acton program form a vast web of interconnected actors. Some actors are on the edge of the Acton realm, bordering to the external world where they may be doing I/O with external entities, through files, sockets or other means. All I/O is callback based and thus event driven and reactive. When there is an event, an actors reacts, perhaps initiating calls to other actors. A ripple runs across the web of actors, each reacting to incoming messages and acting accordingly.
+ +after
and sleepIn many languages, it is fairly common to use sleep()
for things like timeouts, pacing and similar. In Acton, and more generally in async actor based languages, sleeping is frowned upon and often not even available.
The idiomatic control pattern in Acton is using after
, like after 42.1337: foo()
. This tells the run time system (RTS) to schedule the execution of the foo()
function after 42.1337
seconds. Meanwhile, other methods on the actor can be invoked.
Source:
+import time
+
+"""Pace the sending of messages to once a second
+"""
+
+actor Receiver():
+ def recv(msg):
+ print("At " + str(time.now()) + ", I received a message:", msg)
+
+actor main(env):
+ var i = 0
+ r = Receiver()
+
+ def send_msg():
+ # Send a message, increment i
+ r.recv("Hello " + str(i))
+ i += 1
+
+ # ... and reschedule execution of ourselves in 1 second
+ after 1: send_msg()
+
+ # Exit after awhile
+ if i > 4:
+ env.exit(0)
+
+ # Kick off the whole thing
+ send_msg()
+
+Compile and run:
+actonc after_pace.md
+
+Since the output includes time, you will naturally get a slightly different result if you run this.
+Output:
+At 2023-05-16T10:08:59.135806428+02, I received a message: Hello 0
+At 2023-05-16T10:09:00.136484032+02, I received a message: Hello 1
+At 2023-05-16T10:09:01.135585727+02, I received a message: Hello 2
+At 2023-05-16T10:09:02.135695030+02, I received a message: Hello 3
+At 2023-05-16T10:09:03.135811176+02, I received a message: Hello 4
+
+There is in fact a sleep
function in Acton, hidden away in the acton.rts
module. Do NOT use it! It is intended only for debugging of the RTS itself and will probably disappear from the standard library before 1.0. Despite it, we consider the language to not have a sleep.
Actors should either be actively processing or at rest. Conceptually, a sleep is an active wait, in the sense that the RTS will just sit there waiting for the sleep to finish, it is blocked, while it really could process something else in between, like run a different actor method continuation. Similarly, the actor itself could have had other methods on it invoked instead of being actively blocked on a sleep. Being blocked is very bad, which is why all I/O is asynchronous in Acton and why there is no sleep
.
sleep
is evil, use after
!
Iteration is a core concept in programming and for loops are perhaps the most well known.
+Acton supports the for in
construct to iterate through an Iterator
.
Source:
+
+actor main(env):
+
+ for n in range(1, 100, 1):
+ if n % 15 == 0:
+ print("fizzbuzz")
+ elif n % 3 == 0:
+ print("fizz")
+ elif n % 5 == 0:
+ print("buzz")
+ else:
+ print(n)
+
+ env.exit(0)
+
+Compile and run:
+actonc while.act
+
+Note that the output is random and you could get a different result.
+Output:
+1
+2
+fizz
+4
+buzz
+fizz
+7
+8
+fizz
+buzz
+11
+fizz
+13
+14
+fizzbuzz
+16
+17
+fizz
+19
+buzz
+fizz
+22
+23
+fizz
+buzz
+26
+fizz
+28
+29
+fizzbuzz
+31
+32
+fizz
+34
+buzz
+fizz
+37
+38
+fizz
+buzz
+41
+fizz
+43
+44
+fizzbuzz
+46
+47
+fizz
+49
+buzz
+fizz
+52
+53
+fizz
+buzz
+56
+fizz
+58
+59
+fizzbuzz
+61
+62
+fizz
+64
+buzz
+fizz
+67
+68
+fizz
+buzz
+71
+fizz
+73
+74
+fizzbuzz
+76
+77
+fizz
+79
+buzz
+fizz
+82
+83
+fizz
+buzz
+86
+fizz
+88
+89
+fizzbuzz
+91
+92
+fizz
+94
+buzz
+fizz
+97
+98
+fizz
+
+
+ Acton supports the if
/ elif
/ else
construct - the corner stone of programming control flow.
The conditionals evaluated by if
/ elif
/ else
are expressions.
Source:
+
+def whatnum(n):
+
+ if n < 0:
+ print(n, "is negative")
+ elif n > 0:
+ print(n, "is positive")
+ else:
+ print(n, "is zero")
+
+
+def inrange(n):
+ if n < 10 and n > 5:
+ print(n, "is between 5 and 10")
+ else:
+ print(n, "is outside of the range 5-10")
+
+actor main(env):
+
+ whatnum(5)
+ whatnum(1337)
+ whatnum(-7)
+ whatnum(0)
+
+ inrange(3)
+ inrange(-7)
+ inrange(7)
+
+ env.exit(0)
+
+Compile and run:
+actonc if_else.act
+
+Note that the output is random and you could get a different result.
+Output:
+5 is positive
+1337 is positive
+-7 is negative
+0 is zero
+3 is outside of the range 5-10
+-7 is outside of the range 5-10
+7 is between 5 and 10
+
+
+ The while
construct can be used to run a loop while a condition is true.
Source:
+import random
+
+def throw_dice():
+ number = random.randint(1,6)
+ print("Dice:", number)
+ return number
+
+actor main(env):
+ var luck = True
+
+ while luck:
+ if throw_dice() == 4:
+ # ran out of luck, nobody likes a 4
+ luck = False
+
+ env.exit(0)
+
+Compile and run:
+actonc while.act
+
+Note that the output is random and you could get a different result.
+Output:
+Dice: 3
+Dice: 1
+Dice: 5
+Dice: 2
+Dice: 4
+
+
+ The environment of an Acton application is the outside world. Any useful application typically needs to interact with the environment in some way, like reading arguments or taking input from stdin and printing output.
+ +For interactive programs, like a text editor, input is not fed into the program +line by line, rather the program can react on individual key strokes.
+The default stdin mode is the canonical mode, which implies line buffering and +that there are typically line editing capabilities offered that are implemented +external to the Acton program. By setting stdin in non-canonical mode we can +instead get the raw key strokes directly fed to us.
+actor main(env):
+ def interact(input):
+ print("Got some input:", input)
+
+ # Set non-canonical mode, so we get each key stroke directly
+ env.set_stdin(canonical=False)
+ # Turn off terminal echo
+ env.set_stdin(echo=False)
+ env.stdin_install(interact)
+
+We can also disable the echo mode with the echo option.
+The Acton run time system will copy the stdin terminal settings on startup and +restore them on exit, so you do not need to manually restore terminal echo for +example.
+ +Read input from stdin by installing a handler for stdin data. The returned data is str
actor main(env):
+ def interact(input):
+ print("Got some input:", input)
+
+ env.stdin_install(interact)
+
+It is possible to specify the encoding and an on_error() callback which is invoked if there are problem with decoding the data. When encoding is not specified (default None
), an attempt is made to discover the encoding by reading the LANG
environment variable. If no encoding is discovered, the default is to use utf-8
.
actor main(env):
+ def interact(input):
+ print("Got some input:", input)
+
+ def on_stdin_error(err, data):
+ print("Some error with decoding the input data:", err)
+ print("Raw bytes data:", data)
+
+ env.stdin_install(on_stdin=interact, encoding="utf-8", on_error=on_stdin_error)
+
+You can read the raw data in bytes
form by installing a bytes handler instead:
actor main(env):
+ def interact(bytes_input):
+ # Note how the input might contain parts (some bytes) of a multi-byte
+ # Unicode character in which case decoding will fail
+ print("Got some input:", bytes_input.decode())
+
+ env.stdin_install(on_stdin_bytes=interact)
+
+This allows reading binary data and more explicit control over how to decode the data.
+ +It is possible to read, set and unset environment variables. The standard functions env.getenv
, env.setenv
and env.unsetenv
all assume str
input and output, which is a convenience based on the assumption that all data is encoded using UTF-8. POSIX systems really use binary encoding for both environment names and variables. To access the environment as bytes and handle decoding explicitly, use env.getenvb
, env.setenvb
and env.unsetenvb
.
Source:
+actor main(env):
+ env_user = env.getenv("USER")
+ if env_user is not None:
+ print("User:", env_user)
+ env.setenv("FOO", "bar")
+ env.unsetenv("LANG")
+ foo_env = env.getenv("FOO")
+ if foo_env is not None:
+ print("FOO:", foo_env)
+ env.exit(0)
+
+Output:
+User: myuser
+FOO: bar
+
+
+ This is a guide to integrating C libraries in Acton code. We will use the zlib compression library, written in C, to build an Acton module that supports zlib compression and decompression.
+We will only focus on the inflate
and deflate
functions in zlib. They are pure functions (meaning they only take some input and return some output, they do not have any side effects like writing to some shared state), that makes them easier to integrate than anything that does I/O. While zlib does expose functions to interact with files, we don't want to reimplement file related functionality since we already have this supported by the Acton stdlib.
Let's start by making a new Acton project, let's call it acton-zlib
. New projects are created with an example "Hello world" app. Let's remove it and start from scratch.
acton new acton-zlib
+cd acton-zlib
+rm src/*
+
+The Acton compiler parses .act source code, runs through all its compilation passes with type checking, CPS conversion, lambda lifting etc and finally produces C code. Internally, Acton then uses the Zig build system to compile the generated C code to libraries and finally binary executables.
+To add a C library dependency, it first needs to be buildable using the Zig build system, which means that it needs a build.zig
file, the config file for the Zig build, somewhat similar to the CMakeLists.txt of CMake. Some projects have already adopted a build.zig
in the upstream repo, like PCRE2 and the Boehm-Demers-Weiser GC (both of which are used by Acton). In some cases, there are forks of projects with build.zig
added. Otherwise you will need to write one for yourself, which is usually simpler than it might first seem.
In the case of zlib, there is already a repo available with a build.zig for zlib. Navigate to the Tags page, find 1.3.1
and the link to the source files, i.e. https://github.com/allyourcodebase/zlib/archive/refs/tags/1.3.1.tar.gz
.
Add it to our acton-zlib
project:
acton zig-pkg add https://github.com/allyourcodebase/zlib/archive/refs/tags/1.3.1.tar.gz zlib --artifact z
+
+Note the --artifact z
which is provided to instruct which library to link with. Headers from the zlib library, like zlib.h
, will now become visible to C files in our project and the z
library will be linked in with our executables. The easiest way to discover what the artifacts are called is by inspecting the build.zig
file of the package. This particular zlib build.zig
starts like this:
const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+ const upstream = b.dependency("zlib", .{});
+ const lib = b.addStaticLibrary(.{
+ .name = "z",
+ .target = b.standardTargetOptions(.{}),
+ .optimize = b.standardOptimizeOption(.{}),
+ });
+ lib.linkLibC();
+ lib.addCSourceFiles(.{
+ .root = upstream.path(""),
+ .files = &.{
+ "adler32.c",
+ "crc32.c",
+...
+
+It is the .name
argument to addStaticLibrary
that tells us the name of the artifact. Zig packages might expose multiple such artifacts, as is the case for mbedtls.
acton zig-pkg add
will fetch the package from the provided URL and save the hash sum to build.act.json
, resulting in:
{
+ "dependencies": {},
+ "zig_dependencies": {
+ "zlib": {
+ "url": "https://github.com/allyourcodebase/zlib/archive/refs/tags/1.3.1.tar.gz",
+ "hash": "122034ab2a12adf8016ffa76e48b4be3245ffd305193edba4d83058adbcfa749c107",
+ "artifacts": [
+ "z"
+ ]
+ }
+ }
+}
+
+Next up we need to create the Acton zlib
module. Open src/zlib.act
and add a compress and decompress function:
pure def compress(data: bytes) -> bytes:
+ NotImplemented
+
+pure def decompress(data: bytes) -> bytes:
+ NotImplemented
+
+The NotImplemented
statement tells the compiler that the implementation is not written in Acton but rather external. When there is a .ext.c
file, the compiler expects it to contain the implementations for the NotImplemented
functions. Also note the explicit types. Normally the Acton compiler can infer types, but since there is no Acton code here, only C code, there is nothing to infer from.
Now create src/zlib.ext.c
which is where we will do the actual implementation of these functions. We need to add a __ext_init__
function, which runs on module load by the Acton RTS, which must always exist. There is nothing to do in particular for zlib so let's just create an empty function, like so:
void zlibQ___ext_init__() {}
+
+Next, we need to fill in the C functions that map to the Acton functions compress
and decompress
. By invoking acton build
we can get the compiler to generate a skeleton for these. We will also get a large error message, since there is no actual implementation:
user@host$ acton build
+... some large error message
+
+Ignore the error and instead check the content of out/types/zlib.c
and we will find the C functions we need, commented out:
#include "rts/common.h"
+#include "out/types/zlib.h"
+#include "src/zlib.ext.c"
+B_bytes zlibQ_compress (B_bytes data);
+/*
+B_bytes zlibQ_compress (B_bytes data) {
+ // NotImplemented
+}
+*/
+B_bytes zlibQ_decompress (B_bytes data);
+/*
+B_bytes zlibQ_decompress (B_bytes data) {
+ // NotImplemented
+}
+*/
+int zlibQ_done$ = 0;
+void zlibQ___init__ () {
+ if (zlibQ_done$) return;
+ zlibQ_done$ = 1;
+ zlibQ___ext_init__ ();
+}
+
+Copy the commented-out skeleton into our own src/zlib.ext.c
. Just in order to get something that compiles, let's just quickly let the functions return the input data. Since both input and output are bytes
, this should now compile (and work at run time).
B_bytes zlibQ_compress (B_bytes data) {
+ return data;
+}
+B_bytes zlibQ_decompress (B_bytes data) {
+ return data;
+}
+
+user@host:~/acton-zlib$ acton build
+Building project in /Users/user/acton-zlib
+ Compiling zlib.act for release
+ Finished compilation in 0.005 s
+ Compiling test_zlib.act for release
+ Finished compilation in 0.019 s
+ Final compilation step
+user@host:~/acton-zlib$
+
+Before we implement the body of the compress and decompress functions, we can write a small test module which will tell us when we've succeeded. We use some pre-known test data (which we could get from another language implementation):
+import testing
+import zlib
+
+def _test_roundtrip():
+ for x in range(100):
+ i = "hello".encode()
+ c = zlib.compress(i)
+ d = zlib.decompress(c)
+ testing.assertEqual(i, d)
+
+def _test_compress():
+ for x in range(100):
+ i = "hello".encode()
+ c = zlib.compress(i)
+ testing.assertEqual(c, b'x\x9c\xcbH\xcd\xc9\xc9\x07')
+
+def _test_decompress():
+ for x in range(1000):
+ c = b'x\x9c\xcbH\xcd\xc9\xc9\x07'
+ d = zlib.decompress(c)
+ testing.assertEqual(d, b'hello')
+
+Note how we run a few test iterations to get slightly better timing measurements for performance testing. Run the test with acton test
:
user@host:~/acton-zlib$ acton test
+
+Tests - module test_zlib:
+ decompress: FAIL: 195 runs in 50.728ms
+ testing.NotEqualError: Expected equal values but they are non-equal. A: b'x\x9c\xcbH\xcd\xc9\xc9\x07' B: b'hello'
+ compress: FAIL: 197 runs in 50.886ms
+ testing.NotEqualError: Expected equal values but they are non-equal. A: b'hello' B: b'x\x9c\xcbH\xcd\xc9\xc9\x07'
+ roundtrip: OK: 226 runs in 50.890ms
+
+2 out of 3 tests failed (26.354s)
+
+user@host:~/acton-zlib$
+
+As expected, the roundtrip test goes through, since we just return the input data while the compress and decompress tests fail.
+Now let's fill in the rest of the owl. Below is the body of the zlibQ_compress
function. The bulk of this code is not particularly interesting to this guide as it has more to do with standard C usage of zlib, but a few things are worth noting.
B_bytes zlibQ_compress(B_bytes data) {
+ if (data->nbytes == 0) {
+ return data;
+ }
+
+ // Prepare the zlib stream
+ int ret;
+ z_stream stream;
+ memset(&stream, 0, sizeof(stream));
+ ret = deflateInit(&stream, Z_DEFAULT_COMPRESSION);
+ if (ret != Z_OK) {
+ $RAISE((B_BaseException)$NEW(B_ValueError, to$str("Unable to compress data, init error: %d", ret)));
+ }
+
+ // Set the input data
+ stream.avail_in = data->nbytes;
+ stream.next_in = (Bytef*)data->str;
+
+ // Allocate the output buffer using Acton's malloc
+ size_t output_size = deflateBound(&stream, data->nbytes);
+ Bytef* output_buffer = (Bytef*)acton_malloc_atomic(output_size);
+ stream.avail_out = output_size;
+ stream.next_out = output_buffer;
+
+ // Perform the deflate operation
+ ret = deflate(&stream, Z_FINISH);
+ if (ret != Z_STREAM_END) {
+ $RAISE((B_BaseException)$NEW(B_ValueError, $FORMAT("Unable to compress data, error: %d", ret)));
+ }
+
+ // Clean up
+ deflateEnd(&stream);
+
+ return actBytesFromCStringNoCopy(output_buffer);
+}
+
+Memory management is always top of mind when writing C, as it the case here. We can allocate memory via the Acton GC-heap malloc or just plain malloc()
(the non-GC heap, to be explicit). Since zlibQ_compress
is pure, we have no state leaking out of the function other than via its return value. All return values must be allocated on the Acton GC heap, so we know we must use acton_malloc
for any value that we return. Any other local variables within the function can use classic malloc, as long as we make sure to explicitly free it up. For class or actor methods, any allocation for class or actor attributes must be performed using the Acton GC malloc, since there is no destructor or similar where a free can be inserted, so using classic malloc would be bound to leak. Also note that in this particular case, we know that the returned bytes value itself is not going to contain any pointers, so by using acton_malloc_atomic
we can get a chunk of memory that will not be internally scanned by the GC, which saves a bit of time and thus improves GC performance. If we allocate structs that do carry pointers, they must use the normal acton_malloc()
.
actBytesFromCStringNoCopy(output_buffer)
takes the buffer
(already allocated via acton_malloc_atomic()
) and wraps it up as a boxed value of the type B_bytes
that we return.
Also note how we convert Zlib errors to Acton exceptions where necessary.
+Running the test, the compress
test now passes while roundtrip has stopped working (since decompress is not implemented yet):
user@host:~/acton-zlib$ acton test
+Tests - module test_zlib:
+ decompress: FAIL: 158 runs in 50.175ms
+ testing.NotEqualError: Expected equal values but they are non-equal. A: b'x\x9c\xcbH\xcd\xc9\xc9\x07' B: b'hello'
+ compress: OK: 167 runs in 50.225ms
+ roundtrip: FAIL: 147 runs in 50.266ms
+ testing.NotEqualError: Expected equal values but they are non-equal. A: b'hello' B: b'x\x9c\xcbH\xcd\xc9\xc9\x07'
+
+2 out of 3 tests failed (0.941s)
+
+user@host:~/acton-zlib$
+
+Much like the compress function, the decompress function mostly relates to how zlib itself and its interface works. We use the same wrappers and transform errors to exceptions.
+B_bytes zlibQ_decompress(B_bytes data) {
+ if (data->nbytes == 0) {
+ return data;
+ }
+
+ // Prepare the zlib stream
+ int ret;
+ z_stream stream;
+ memset(&stream, 0, sizeof(stream));
+
+ ret = inflateInit(&stream);
+ if (ret != Z_OK) {
+ $RAISE((B_BaseException)$NEW(B_ValueError, $FORMAT("Unable to decompress data, init error: %d", ret)));
+ }
+
+ // Set the input data
+ stream.avail_in = data->nbytes;
+ stream.next_in = (Bytef*)data->str;
+
+ // Allocate the output buffer using Acton's malloc
+ size_t output_size = 2 * data->nbytes; // Initial output buffer size
+ Bytef* output_buffer = (Bytef*)acton_malloc_atomic(output_size);
+ memset(output_buffer, 0, output_size);
+ stream.avail_out = output_size;
+ stream.next_out = output_buffer;
+
+ // Perform the inflate operation, increasing the output buffer size if needed
+ do {
+ ret = inflate(&stream, Z_NO_FLUSH);
+ if (ret == Z_BUF_ERROR) {
+ // Increase the output buffer size and continue decompressing
+ size_t new_output_size = output_size * 2;
+ output_buffer = (Bytef*)acton_realloc(output_buffer, new_output_size);
+ stream.avail_out = new_output_size - stream.total_out;
+ stream.next_out = output_buffer + stream.total_out;
+ } else if (ret != Z_OK) {
+ $RAISE((B_BaseException)$NEW(B_ValueError, $FORMAT("Unable to decompress data, error: %d", ret)));
+ }
+ } while (ret == Z_BUF_ERROR);
+
+ // Clean up
+ inflateEnd(&stream);
+
+ return actBytesFromCStringNoCopy(output_buffer);
+}
+
+user@host:~/acton-zlib$ acton test
+
+Tests - module test_zlib:
+ decompress: OK: 42 runs in 51.065ms
+ compress: OK: 25 runs in 50.032ms
+ roundtrip: OK: 24 runs in 50.053ms
+
+All 3 tests passed (0.738s)
+
+user@host:~/acton-zlib$
+
+And with that, we're done! A simple wrapper around zlib, which is also available on GitHub if you want to study it further.
+ +Functions are declared using the def
keyword.
Use return foo
to return variable foo
. If no return
keyword is used or a lone return
without argument is given, the function will return None
.
Source:
+def multiply(a, b):
+ print("Multiplying", a, "with", b)
+ return a*b
+
+actor main(env):
+ result = multiply(3, 4)
+ print("Result:", result)
+ env.exit(0)
+
+Output:
+Multiplying 3 with 4
+Result: 12
+
+
+ Actor methods are declared under an actor
using the def
keyword.
An actor method runs in the context of the actor and can access its private state. As Actors are sequential processes, calling other methods on the local actor or any function is going to be run sequentially.
+Calling an actor method on the local actor can be done simply by calling it by its name, without any prefix such as self.
.
All actor methods are public. Call a method on another actor by calling actor_name.method_name()
. Calling methods on other actors can be done synchronously or asynchronously.
Source:
+
+def multiply(a, b):
+ print("Multiplying", a, "with", b)
+ return a*b
+
+actor main(env):
+ var secret = 42
+
+ def compute(a):
+ print("Computing result based on our secret", secret)
+ res = multiply(a, secret)
+ return res
+
+ result = compute(3)
+ print("Result:", result)
+ env.exit(0)
+
+Output:
+Computing result based on our secret 42
+Multiplying 3 with 42
+Result: 126
+
+
+ Acton supports higher order functions which means you can pass a function as an argument to another function.
+Source:
+def multiply_with_3(a):
+ print("Multiplying with 3")
+ return 3*a
+
+def multiply_with_42(a):
+ print("Multiplying with 42")
+ return 42*a
+
+def compute(a, fun):
+ """Compute value from a using function fun"""
+ return fun(a)
+
+actor main(env):
+ print( compute(7, multiply_with_3) )
+ print( compute(7, multiply_with_42) )
+ env.exit(0)
+
+Output:
+Multiplying with 3
+21
+Multiplying with 42
+294
+
+
+ We follow tradition and introduce Acton with the following minimal example
+Source:
+# This is a comment, which is ignored by the compiler.
+
+# An actor named 'main' is automatically discovered and recognized as the root
+# actor. Any .act file with a main actor will be compiled into a binary
+# executable and the main actor becomes the starting point.
+actor main(env):
+ print("Hello World!")
+ env.exit(0)
+
+Compile and run:
+acton hello.act
+./hello
+
+Output:
+Hello World!
+
+When an Acton program runs, it really consits of a collection of actors that interact with each other. In the above example, we have just a single actor, which has been given the name main
and that acts as the root actor of our system. The root actor of a system takes a parameter env
, which represents the execution environment. env
has methods for accessing command line arguments and carries a reference to the capabilities of the surrounding world, WorldCap
, for accessing the environment, e.g. reading from and writing to keyboard/screen and files, working with sockets etc.
Writing tests is an integral part of writing software. In an Acton project, you can run all the tests by issuing acton test
:
foo@bar:~hello$ acton test
+
+Tests - module hello:
+ foo: OK: 278 runs in 50.238ms
+
+All 1 tests passed (23.491s)
+
+foo@bar:~hello$
+
+See the Testing section on how to write tests.
+ +While Acton is a compiled language and the acton
compiler produces an executable binary, script style execution is also possible through the use of a shebang line.
Source:
+#!/usr/bin/env runacton
+
+actor main(env):
+ print("Hello World!")
+ env.exit(0)
+
+Ensure the executable bit is set and run your .act file directly:
+chmod a+x hello.act
+./hello.act
+
+Output:
+Hello World!
+
+
+ Acton is a advanced general purpose programming language offering functional and object-oriented style of programming based on the actor-model and async I/O. Type safe and with capabilities based security, Acton is statically compiled for high performance and portability. In other words, pretty much perfect ;) We hope you enjoy it as much as we do. It's readily available to build anything from advanced "shell scripts" to low level databases.
+Unique among programming languages, Acton offers orthogonal persistence, which means you don't have to think about how to persist data, or rather the state of your program, for durability. Acton will do it for you, using its fault-tolerant distribute database. Pretty damn cool!
+ +