Сайт: https://ziglang.org
Zig is a general-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
— Официальный сайт Zig
Zig — это одна из попыток сделать better C. Он быстрый как C и совместим с C-библиотеками без особой боли. Это простой, небольшой язык, с мощными возможностями в compile-time, обработкой ошибок без исключений, и быстрой компиляцией. Он не безопасный (можно легко разыменовать невалидный указатель), но предотвращает некоторые из частых проблем C.
const std = @import("std");
const Header = struct {
magic: u32,
name: []const u8,
};
pub fn main() void {
printInfoAboutStruct(Header);
}
fn printInfoAboutStruct(comptime T: type) void {
const info = @typeInfo(T);
inline for (info.Struct.fields) |field| {
std.debug.print(
"{s} has a field called {s} with type {s}\n",
.{
@typeName(T),
field.name,
@typeName(field.field_type),
},
);
}
}
-
Zig — строго, статически типизированный язык. Компилируется в нативные бинарники или в WASM. Не поддерживает какого-либо рода полиморфизма: нет ни наследования, ни интерфейсов/трейтов. Вместо этого есть compile-time рефлексия, которая позволяет получить информацию о типе во время компиляции. Так, например, работает встроенная функция
print()
:switch (@typeInfo(T)) { .ComptimeInt, .Int, .ComptimeFloat, .Float => { return formatValue(value, actual_fmt, options, writer); }, .Void => { return formatBuf("void", options, writer); }, .Bool => { return formatBuf(if (value) "true" else "false", options, writer); }, ... }
-
Zig порождает статические (не завязанные даже на libc) бинарники. Компилятор Zig является также компилятором C, и тоже линкует программы с libc статически.
-
Дефолтного менеджера зависимостей нет, поэтому говорить про размер экосистемы довольно сложно.
-
В Zig крайне богатые возможности для исполнения кода во время компиляции. Почти любую Zig-функцию можно запустить на этапе компиляции с помощью ключевого слова
comptime
. Более того, функции могут принимать типы как значения. Таким образом, вместо специального синтаксиса для дженериков, в Zig есть просто функции, которые принимают тип и возвращают тип. Так, например, выглядит объявление встроенного типаArrayList
:pub fn ArrayList(comptime T: type) type { return ArrayListAligned(T, null); }
-
Некоторые флаги оптимизации в Zig вынесены в ключевые слова: например, слово
inline
перед циклом указывает компилятору, что этот цикл следует размотать (unroll). -
В Zig есть ADT. В отличие от большинства языков с ADT, требуется вручную поддерживать отдельно
enum
для тега и отдельноunion
для значений:const ComplexTypeTag = enum { ok, not_ok, }; const ComplexType = union(ComplexTypeTag) { ok: u8, not_ok: void, };
-
Обработка ошибок осуществляется с помощью специального типа
error
. Функция может вернуть error union, который содержит либо ошибку, либо успех. Несмотря на то, что в Zig есть ADT, error union — это специальный тип. Все ошибки с одинаковым названием равны:error.Abc
это одно и то же значение в любой части программы. Максимальное количество разных ошибок в программе — 65535.Для обработки error unions есть специальный оператор
catch
, который позволяет предоставить альтернативное значение в случае ошибки (например,res catch 0
будет равно нулю, еслиres
содержит ошибку) или код обработки. Сокращениеtry expr
— синтаксический сахар дляexpr catch |err| return err
.Ошибки сохраняют трейсбек по мере того, как их возвращают из функций.
-
Есть
defer
, как в Go, и более специфичныйdefererr
, который выполняется только в том случае, если функция вернула ошибку. -
Асинхронность в Zig бесцветная: весь IO становится асинхронным, если выставить глобальную переменную
const io_mode = .evented
. Кейвордыasync
/await
существуют, но работают не так, как в большинстве других языков —async
отправляет некое вычисление в бэкграунд (возвращая хендлер), аawait
позволяет дождаться его выполнения.Более того,
async
/await
всё ещё работают, даже если сами системные вызовы синхронные. Правда, для многих асинхронных программ перевод их в blocking-режим может породить дедлоки.Больше про асинхронность в Zig:
-
Все функции стандартной библиотеки, которые выделяют память, принимают аллокатор в явном виде и возвращают ошибки аллокации, если они происходят. Это может быть несколько вербозно, но зато спасает от проблем при использовании в системном коде, вроде тех, что возникли при интеграции Rust в curl.
-
Zig — не безопасный язык. Указатели не могут быть нулевыми, но Zig никак не проверяет, что объект, на который указывает указатель ещё жив, или вообще был аллоцирован.
Сайт: https://www.idris-lang.org
Idris is a programming language designed to encourage Type-Driven Development. In type-driven development, types are tools for constructing programs. We treat the type as the plan for a program, and use the compiler and type checker as our assistant, guiding us to a complete program that satisfies the type. The more expressive the type is that we give up front, the more confidence we can have that the resulting program will be correct.
— Официальный сайт Idris
Idris — один из немногих существующих языков, который предоставляет зависимые типы. Зависимые типы позволяют выразить зависимость между значением (термом) на входе у функции и типом на выходе. Например:
-- Мы определяем функцию, которая принимает значение типа `Bool` и возвращает тип
funcType : Bool -> Type
-- Пусть этим типом будет строка, если на входе было значение True
funcType True = String
-- И число, если на входе было False
funcType False = Int
-- Мы говорим, что `func` принимает два аргумента: значение типа `Bool` и значение
-- того типа, который получится, если функции `funcType` передать первый аргумент.
func : (b : Bool) -> (funcType b)
-- Как можно видеть, она возвращает разные типы значений в зависимости от своих аргументов
func True = "Hello!"
func False = 42
Это не то же самое, что comptime-функции в Zig: в отличие от comptime-функций, зависимые типы
могут зависеть от значений, получаемых в рантайме (например, от пользователя). Первым аргументом
функции func
может быть значение, полученное с клавиатуры.
Помимо этого, у Idris есть довольно богатые возможности по формальной верификации кода.
Как Haskell, но с одинарными двоеточиями.
module BTree
public export
data BTree a = Leaf
| Node (BTree a) a (BTree a)
export
insert : Ord a => a -> BTree a -> BTree a
insert x Leaf = Node Leaf x Leaf
insert x (Node l v r) = if (x < v) then (Node (insert x l) v r)
else (Node l v (insert x r))
export
toList : BTree a -> List a
toList Leaf = []
toList (Node l v r) = BTree.toList l ++ (v :: BTree.toList r)
export
toTree : Ord a => List a -> BTree a
toTree [] = Leaf
toTree (x :: xs) = insert x (toTree xs)
Если мы только не говорим про доказательство теорем.
helpEven : (j : Nat) -> Parity (S j + S j) -> Parity (S (S (plus j j)))
helpEven j p = rewrite plusSuccRightSucc j j in p
helpOdd : (j : Nat) -> Parity (S (S (j + S j))) -> Parity (S (S (S (j + j))))
helpOdd j p = rewrite plusSuccRightSucc j j in p
parity : (n:Nat) -> Parity n
parity Z = Even {n=Z}
parity (S Z) = Odd {n=Z}
parity (S (S k)) with (parity k)
parity (S (S (j + j))) | Even = helpEven j (Even {n = S j})
parity (S (S (S (j + j)))) | Odd = helpOdd j (Odd {n = S j})
-
Idris умеет исполнять на уровне типов любую тотальную (т. е. ту, про которую компилятор может доказать, что она завершается (halt) функцию). Из этого очевидно, что система типов в Idris не Тьюринг-полная.
Любую Idris-функцию можно пометить как тотальную с помощью аттрибута
%total
, или даже сделать это для всех функций с помощью%default total
.Если функция в аннотации типа не тотальная (или Idris не может доказать, что она тотальная), компилятор просто оставит её неприменённой. Это можно увидеть, например, если заменить в примере из Summary
Bool
наString
. -
Idris — классический чисто функциональный язык вроде Haskell. Использует монады для IO.
-
Поддерживается полиморфизм через тайпклассы, которые здесь называются интерфейсами.
-
В отличие от Haskell, в Idris есть перегрузка функций.
-
Один из самых известных типов в Idris — это
Vect n a
. Вектор содержит в себе информацию не только о типе, лежащем внутри, но и о длине вектора, что позволяет проверять операции вроде обращения по индексу на этапе компиляции. -
Тип натуральных чисел
Nat
определён как перечисление — это либо ноль (Z
), либо число, следующее за другим натуральным числомn
(S n
). Это позволяет удобный паттерн-матчинг по натуральным числам: например, функцияhead
, возвращающая первый элемент вектора, определена дляVect (S _) a
, но не дляVect Z a
. -
Вместо любого значения (или типа, типы — тоже значения) в Idris можно подставить
?дырку
(hole). При проверке типов, компилятор выведет на экран типы всех дырок в программе. Это удобно, когда в какой-то момент непонятно, значение какого типа ожидается. -
Idris 2 (актуальная версия) использует рантайм Scheme.
-
В Idris 2 есть линейные типы, которые позволяют гарантировать, что значение используется ровно один раз. Это полезно для проверки корректности использования ресурсов: например, взятый лок можно использовать ровно один раз, отпустив его (и получив свободный лок). Аналогично, свободный лок можно ровно один раз взять, получив взятый лок.
-
Idris предоставляет инструментарий для доказательства теорем про исходный код программы.
-
Про Idris есть книга
Type-Driven Development with Idris
, которая умеет объяснять Idris куда лучше, чем я. К этой книге есть дополнение, исправляющее её для совместимости с Idris 2. Кроме того, в открытом доступе существует туториал по Idris 2.
Страница на Esolangs: https://esolangs.org/wiki////
///
— это язык, полностью основанный на поиске и замене строк в тексте. Он Тьюринг-полный,
потому что на нём можно выразить интерпретатор языка Bitwice Cyclic Tag. Поддерживает
вывод, но не ввод.
/]
[///#/ bottles of beer on the wall,
//$/ bottles of beer
Take one down, pass it around
//%/ bottles of beer on the wall.
/99#99$98%98#98$97%97#97$96%96#96$95%95#95$94%94#94$93%93]
[#93$92%92#92$91%91#91$90%90#90$89%89#89$88%88#88$87%87#87]
[$86%86#86$85%85#85$84%84#84$83%83#83$82%82#82$81%81#81$80]
[%80#80$79%79#79$78%78#78$77%77#77$76%76#76$75%75#75$74%74]
[#74$73%73#73$72%72#72$71%71#71$70%70#70$69%69#69$68%68#68]
[$67%67#67$66%66#66$65%65#65$64%64#64$63%63#63$62%62#62$61]
[%61#61$60%60#60$59%59#59$58%58#58$57%57#57$56%56#56$55%55]
[#55$54%54#54$53%53#53$52%52#52$51%51#51$50%50#50$49%49#49]
[$48%48#48$47%47#47$46%46#46$45%45#45$44%44#44$43%43#43$42]
[%42#42$41%41#41$40%40#40$39%39#39$38%38#38$37%37#37$36%36]
[#36$35%35#35$34%34#34$33%33#33$32%32#32$31%31#31$30%30#30]
[$29%29#29$28%28#28$27%27#27$26%26#26$25%25#25$24%24#24$23]
[%23#23$22%22#22$21%21#21$20%20#20$19%19#19$18%18#18$17%17]
[#17$16%16#16$15%15#15$14%14#14$13%13#13$12%12#12$11%11#11]
[$10%10#10$9%9#9$8%8#8$7%7#7$6%6#6$5%5#5$4%4#4$3%3#3$2%2#2]
[$1 bottle of beer on the wall.
1 bottle of beer on the wall,
1 bottle of beer
Take one down, pass it around
No more bottles of beer on the wall.
-
Программа на
///
выполняется, пока она не пуста. Действие определяется по первому символу программы:Символ Действие /
Парсит команду в формате /PATTERN/REPLACEMENT/
и заменяетPATTERN
наREPLACEMENT
\
Удаляет два первых символа из программы и выводит второй другой Удаляет первый символ из программы и выводит его -
Замена выполняется, пока в строке остаются вхождения
PATTERN
. К примеру, программа/ab/qa/abb
будет выполняться так:
/ab/qa/abb # Парсим /ab/qa/, выполняем первую замену /ab/qa/qab # Замена в самом выражении не происходит /ab/qa/qqa # Сделали вторую замену qqa # Больше вхождений нет, завершили замену qa # Вывели q, удалили символ a # Аналогично # Вывели a. Программа теперь пуста и выполнение завершается
-
PATTERN
— это просто фиксированная строка, не регулярное выражение -
Очевидно, любая программа на
///
, которая не содержит символов/
и\
— квайн.