Kakoune a fresh take on editors
Modal editors are fun
Editing and exploring text in a modal editor is fun. In editors like Kakoune and (Helix)[https://helix-editor.com/] it is even more fun.
Modal editing is flipped on its head; where you in vim you state what you want to do/execute first and then how much of it/where, in kak/hx you first state how much/where and then what you want to do.
In practice this means that you first select an area or text and then you state what you want to do with. Ex deleting two words forward:
- in vim:
d2w
- in kak:
2wd
- in hx:
v2wd
This concept also extends to things like search and replace where you would use macros etc because kak and hx are both built around using multiple cursors. Multiple cursors besides giving clear visual feedback on what you are about to do is also a really fun way to edit text.
Why am I drawn to Kakoune specifically
It is not because Kakoune is written in c++ (helix is written in rust btw) but because it tries to stick to the unix philosopy of
only doing one thing and doing it well. This means that instead of including stuff like file explorers, lsp, treesitter, vcs integrations, tabs, windows,
or even more esoteric stuff like sorting text (which vim has included), or if you want to do a search and replace in a big file where multiple cursors in unwieldy
kakoune makes it easy to integrate with these things and even uncludes utilities to pipe text out to command line ulitities.
For windowing and tabs Kakoune either opens up new windows on the os or even better uses tmux for window handling using its client/server architecture.
Why implement and commit to maintining something that already exists and probably does a better job?
Using the example of sorting text which in vim is build in; you can select the lines of text you want to sort pipe sort
(pipe is per default hotkeyed as |
even)
Kakounes lack of file explorer has lead me to discovering the broot which I didnt know I was missing before I found it,
and is easy to integrate into my kakoune work flow.
So far I have found that using pipe
and even append-output
which runs a shell command and appends its output into the buffer allows
me to work the same within my editor as I do outside of it affording me less time to think about where I am and how to do it and more
about what I am doing. I know this is also possible in vim with ! % and the angled brackets its just more streamlined in Kakoune
where it is “the” way of doing things.
Oh and clippy is back!
»Using a c shared library from zig
Using a c library from zig is quite easy. The zig compiler understands c code so no FFI required.
Create a standard zig project
$ zig init-exe
Find a library and function(s) you would like to use
$ ls /lib/
As an example I will use libc
and the simplest function I can think to use is:
$ nm -C -A /lib/libc.so.6 | grep " printf"
/lib/libc.so.6:0000000000058230 T printf
Link the library in your build.zig file
$ git diff build.zig
diff --git a/build.zig b/build.zig
index 0e4f4b7..66343d9 100644
--- a/build.zig
+++ b/build.zig
@@ -12,6 +12,7 @@ pub fn build(b: *std.build.Builder) void {
const mode = b.standardReleaseOptions();
const exe = b.addExecutable("c_lib_test", "src/main.zig");
+ exe.linkSystemLibrary("c");
exe.setTarget(target);
exe.setBuildMode(mode);
exe.install();
Import and call library in your zig code
The import is done with two builtin functions
const c = @cImport({
@cInclude("stdio.h");
});
Now all functions in stdio resides under the the const c
and can be called like so:
c.printf("Hello world of C!");
Thats it, here is the full program
const c = @cImport({
@cInclude("stdio.h");
});
pub fn main() void {
_ = c.printf("Hello world of C!");
}
$ zig build run
Hello world of C!
A first look at unison language
Its not a philishave
Its unison! Unison is a (as of writing this) alpha release language I am by no means an expert in unison so the following is my summary after a deep dive in the documentation
The lowdown of unison is that it is a functional language that applies the concept of content addressed storage to the how
code and the codebase
is managed. Unlike other languages where the codebase is a set of files in unison the codebase
is a database of content addressable definitions.
At the heart of how the codebase
works are hashed functions. Unison stores and manages these hashes as well as the ast that makes up the program.
One can think of a unison program as a graph where every node is a definition that can refer to other definitions. For the programmer to be able
to work on the codebase
unison can render these ASTs to and from text files.
Hashed functions
In order to enable content addressing unison will hash all your functions. However it does so by unifying the functions so aliases does not effect the hash.
Lets attempt to psudo-hash the following function as unison would do it:
xum : [Nat] -> Nat
sum xs = foldLeft (+) 0 xs
The first thing unison does is to “normalize” the function.
sum | xs | foldLeft | (+) | 0 | xs |
---|---|---|---|---|---|
_ | $arg1 | #abcde1234 | (##Nat.+) | 0 | $arg1 |
- sum - is the function name but we do not care about that because the aim is to create a function name (hash)
- xs - is exchanged for a indexed argument name
- foldLeft - is exchanged for its hash (here represented with just the start of the hash)
- 0 - constants are stored as constants
- (+) - this is a built in function so is a special case of resolving to a namespaced function name
Now the “normalized” representation of the function can be hashed.
Some codebase
selling points
Refactoring aliases does not effect the codebase
Because of the way the functions are hashed unison does not care what the programmers name functions or even if they name them the same thing. Our function above:
sum xs = foldLeft (+) 0 xs
would have the name hash as this function:
tally nums = foldLeft (+) 0 nums
No conflicting dependencies
Imagine that we in a non-unison language have libraries b
c
and d
like so:
A
/ \
b c
\ /
d
Our program is A
and it depends on b
and c
. They in turn depend on d
.
This is fine as long as b
and c
uses the same version of d
or even a compatible one.
However if that ceases to be the case we can no longer build our program with upgraded dependencies.
This is not a problem in unison. In unison the d
would evaluate to a hash and c
or d
can either depend on
the same d
or a different one (between the two).
No builds
Code that exists in the codebase
is already ready to run. The instant you pull a library codebase
its functions can be executed. This in turn enables self deploying code also which is another (large) concept in unison
that is unfortunately as of writing this just a theory and a promise.
Pluggable syntax
While it does not exist currently the codebase
can enable pluggable syntax.
As stated initially there exists tooling in unison to translate its ASTs and function hashes
to human readable text files and back again. Adding a new syntax is simply having a different translation to and from.
A second look at pony language
I last wrote about my initial look at pony. I have since played a bit more with the language and compiled a list of interesting quirks I learned about pony.
Division by 0
Pony wont crash if you devide by 0. Division by 0 is 0 :)
fun a(i: U64): U64 => 10 / i
env.out.print(a(0).string()) // 0
But only if the zero is not known when compiling the function
fun b(): U64 => 10 / 0 // main.pony:2:24: constant divide or rem by zero
Sum types
The sum types of pony are a beauty, and honestly short of Haskell I think the most pleasant to use for me so far in any language :) They are so powerful they even entierly replace the need for Option/Maybe types.
fun c(str: String): (U8 | None) =>
match str
| "one" => 1
| "two" => 2
else None
end
env.out.print(Abc.c("one").string()) // 1
env.out.print(Abc.c("gazzillion").string()) // None
Calling C
The C FFI of pony is fairly straight forward
// import library
use "lib:computersays"
// declare function from library above
// with the correct types
// pony will trust that you get this
// correct. in this case the function is
// called answer, its return type is USize
// and it takes no arguments
use @answer[USize]()
// now we can call the function
// and print its output
env.out.print((@answer().string()) // 42
Interface or Traits
Some languages support either traits/nominal subtyping or interfaces/structural subtyping. Pony supports both.
trait Named
fun name(): String => "Bob"
class Bob is Named
env.out.print(Bob.name()) // "Bob"
Sorry I couldnt think of a better example to illustrate the “structural” part :)
Interfaces also accept the keyword is
which for interfaces with implemented functions
is actually how you would say that this class implements interface Named without
the “structural subtyping” part :)
interface Named
fun name(): String => "Bob"
class Bob is Named
env.out.print(Bob.name()) // "Bob"
Behaviours
Actors can have behaviours be
. These behaviours are how actors communicate.
There is no “await” function like with BEAM languages so a Main actor and
a Counter actor might communicate like so:
actor Counter
var _count: U32
new create() =>
_count = 0
// this is accessable from the outside because it is a `be`
be increment() =>
_count = _count + 1
// a `be` can get an alias to a (in this case an actor with `tag` refcap) passed
be get(main: Main) =>
// we call Mains `be display`
main.display(_count)
actor Main
let _env: Env
// Main is the entrypoint of an executable, Env is passed implicitly
// as new create (the constructor) is called on execute
new create(env: Env) =>
_env = env
var count: U32 = 10
var counter = Counter
for i in Range[U32](0, count) do
counter.increment()
end
counter.get(this)
be display(result: U32) =>
_env.out.print(result.string()) // "10"
First peek at pony language
Today I set out to explore ponylang.
As someone who enjoys writing both elixir, rust and scala ponylang seems to combine all of them into something else. I am especially interested in the ponylangs take on safe sharing trough integrated referential capabilities.
What is pony language
The sales pitch from ponylang.io reads
Pony is an open-source, object-oriented, actor-model, capabilities-secure, high-performance programming language.
Pony promises to be a statically typed, compiled language, on a lightweight runtime with an integrated actor model. Unlike other actor-model-integrated languages like erlang/elixir pony does not use a virtual machine. Pony also attempts to iterate on safe variable sharing from what I am previously used to (immutability) to using referential capabilities (refcaps) thus limiting the overhead.
A first look at referential capabilities
Refcaps is at the core of pony. It is a way of describing read and write permissions given a context trough the type system. To make sense of it I found that this talk really helped.
It explains that data races can be prevented by implementing two laws
:
- read law: If anyone is reading noone else can write
- write law: If anyone is reading noone else can read or write
And that in order to fufull these two laws we can think up four solutions
:
- only allow access from one thread at a time – this can be implemented with a mutex
- allow either many readers or one writer – this can be impemented with a semaphore
- eliminate writers – all state is immutable and aliases never change
- restrict the power that a alias can have so that depending on the alias the holder knows what is and is not allowed with that alias. this is refcaps and pony solution to the datarace problem.
The sharable refcaps
The three refcaps are allowed to share aliases between actors in pony are val
iso
and tag
.
-
val
value means that the data is immutable for this alias (3. eliminating all writers) -
iso
isolated means that there only exists one alias and the holder is free to read and write to is (1. allow access from one thread) -
tag
share the identity of something (cannot read or write)
There exists 3 more refcaps but for getting started with ponylang my experience is that understanding the sharable refcaps will take you far.
Initial takeaway
- Knowing all refcaps in ponylang requires learning 6! types and is an initial hurdle to get over
- Refcaps seem to solve safely solve data access in a more efficient way then just making aliases read only
- Refcaps in pony gives the programmer more ability to reason about objects when reading code