Halka talks to C and to Python. Both are compiled features: halka build
produces a binary that links C directly and, when needed, embeds CPython.
halka run will tell you to build instead.
error[R0030]: `py` interop is a compiled feature — the reference interpreter
cannot call PY code
= help: build it instead: `halka build --release <file.hk>`
The backend emits C99, so a C call is a C call. There is no binding generator and no marshalling layer.
import c "math.h",
import c "stdio.h"
# Declaring the Halka side lets the checker verify the call.
c hypot(x: c double, y: c double): c double
say "hypot(3, 4) = {c hypot(3.0, 4.0)}"
c printf("from C: %d\n", 42)
hypot(3, 4) = 5.0
from C: 42
| Halka | becomes |
|---|---|
import c "math.h" |
#include <math.h> |
import c "./vendor/thing.h" |
#include "./vendor/thing.h" |
c f(args) |
f(args) |
c struct Point: |
a reference to the existing struct Point, never a redefinition |
c double, c int, c size_t … |
exactly those C types |
A declaration with a return type also emits an extern prototype, so a symbol
with no header is still callable. A declaration without one is used only for
checking, and the header must supply the real prototype.
Halka scalars convert implicitly, because the target type is unambiguous:
| Halka | C |
|---|---|
int |
int, long, size_t, int64_t, … |
float |
float, double |
bool |
bool, _Bool |
string |
const char * — no copy; a Halka string is NUL-terminated |
raw *T |
T * |
A C scalar coming back prints and arithmetics like its Halka counterpart. The
checker rejects anything not in this table and asks for an explicit as.
extern:
link: "sqlite3"
link accepts several libraries, comma- or space-separated. The build passes
-lsqlite3 (or sqlite3.lib on MSVC).
The C compiler catches it, and Halka surfaces the warning rather than hiding it:
warning from the C compiler: warning C4133: 'function': incompatible types
these usually mean a `c` declaration does not match the real symbol
This matters. An early version of this FFI emitted extern hk_int hypot(hk_int, hk_int);
for a function that really takes doubles. MSVC only warned, and hypot(3, 4)
returned 4294965466. Silent warnings are how FFI bugs reach production, so
they are printed.
A Halka binary can embed CPython. Every Python object is a py object until
you say what it should become.
import py "math",
import py "builtins"
from py "statistics" import mean
say "sqrt {py math.sqrt(2.0) as float}"
say "fact {py math.factorial(6) as int}"
say "mean {mean([1.0, 2.0, 3.0, 4.0]) as float}"
say "sorted {py builtins.sorted([3, 1, 2]) as list(int)}"
say "upper {py builtins.str.upper("halka") as string}"
Crossing into Python is implicit — a Halka int is unambiguously a Python
int. Crossing back is explicit with as (#15), because a Python value has
no static type and only the program knows what it expects. That is what keeps
the rest of the language statically typed while talking to a dynamic one.
as target |
conversion |
|---|---|
int, float, bool, string |
the obvious one; a failure panics with the Python traceback |
list(int), list(float) |
any Python sequence |
to gives the fallible form and yields a Result — planned, not yet built.
py a.b.c(x) resolves the chain with getattr and calls the last attribute as
a method. from py "m" import f binds f so you can call it directly.
Every conversion and call takes the GIL for its own duration and releases it
immediately. Halka's own parallel: threads keep running on all cores; only
the Python work is serialised, and only while it runs.
That is the point of the whole design:
import py "numpy"
let data: list(float): py numpy.linspace(0.0, 6.28318, n) as list(float)
parallel:
a: kernel(data, 0, q),
b: kernel(data, q, q * 2),
c: kernel(data, q * 2, q * 3),
d: kernel(data, q * 3, n)
NumPy prepares the data; the hot loop is native and runs on every core.
Measured on this machine (examples/ffi/wedge.hk vs examples/ffi/wedge.py,
identical algorithm, same NumPy, same answer 167484.119655):
| Halka | Python | |
|---|---|---|
| kernel, 1 thread | 224 ms | 9 953 ms |
| kernel, 4 threads | 65 ms | 20 745 ms |
| speedup from threads | 3.47x | 0.48x |
Python's threads made it twice as slow. Halka is 44x faster single-threaded and 153x faster with four threads, while still using NumPy for the parts NumPy is good at.
The build needs CPython's development headers.
halka toolchain # shows whether they were found| platform | how |
|---|---|
| Debian / Ubuntu | apt install python3-dev |
| Fedora | dnf install python3-devel |
| macOS, Windows | included with the python.org installer |
Set HALKA_PYTHON to choose a specific interpreter. The prefix of the
interpreter linked against is baked into the binary, so it finds its standard
library without PYTHONHOME being set.
Not implemented. cpp parses and produces E0716. It needs a generated shim
that exposes C++ names through a C ABI and converts exceptions into Result.
toacross a foreign boundary (theResult-yielding form).- Passing Halka functions to C as callbacks (
callback,extern, #38). - Structs by value across the boundary.
- C++ entirely.
- Python: dicts, keyword arguments, and iterating a Python object.