OCaml
| OCaml | |
|---|---|
| Класс языка | объектно-ориентированный, функциональный, мультипарадигмальный, императивный, диалект и свободное и открытое программное обеспечение |
| Появился в | 1996 |
| Автор | Ксавье Лерой и Damien Doligez[вд] |
| Разработчик | INRIA |
| Расширение файлов | .ml или.mli |
| Выпуск | |
| Испытал влияние | SML |
| Лицензия | публичная лицензия Q и LGPL-2.1[вд] |
| Сайт | ocaml.org (англ.) |
| ОС | Unix-подобная операционная система[2] |
OCaml (Objective Caml) —объектно-ориентированныйязык функционального программирования общего назначения, самый распространённый в практической работе диалект языкаML. Был разработан с учётом безопасности исполнения и надёжности программ. Поддерживает функциональную, императивную и объектно-ориентированную парадигмы программирования.
Появился в1996 году под названием Objective Caml, когда Дидье Реми (Didier Rémy) и Жером Вуйон (Jérôme Vouillon) реализовали поддержку объектно-ориентированного программирования для языка Caml, первоначально разработанного во французском институтеINRIA. Официально переименован в OCaml в 2011 году[3].
Инструментарий OCaml включает в себяинтерпретатор,компилятор вбайткод и оптимизирующий компилятор вмашинный код, сравнимый по эффективности сJava и лишь немного уступающий по быстродействиюC иC++[4].
На языке, в частности, написанрендерингформулВикипедии, использующих тег <math>, файлообменный клиентMLDonkey, стек управления гипервизоромXen xapi (является частью Xen Server/Xen Cloud Platform), язык программированияHaxe.
Роль в информатике
[править |править код]Является языком программирования общего назначения, но при этом имеет свои сложившиеся области применения[5].
Во-первых, это — создание «безопасных» (не только в смысле информационной безопасности) приложений. В языке используется сборка мусора, а большинство типов данных является ссылочным (англ. boxed), что означает предотвращениепереполнения буферов во время исполнения программы. Кроме того, статическая типизация и проверки времени компиляции делают невозможными некоторые другие классы ошибок, такие, как ошибкиприведения типов в силу отсутствия автоматического приведения типов. Кроме того, код может бытьформально верифицирован. Имеются утилиты автоматического доказательства типовой корректности кода, превосходящие таковые для большинства языков программирования. И что немаловажно, меры безопасности не влияют на эффективность исполняемого кода[5].
Другой областью успешного применения OCaml являются приложения,управляемые данными (data-driven). К этой области относится обработка текста, а также написание компиляторов. OCaml имеет не только средства для текстовой обработки (какими славится, например,Perl илиAWK), но и инструменты для глубокого семантического анализа и преобразования текста, что делает OCaml применимым в задачахинтеллектуального анализа данных (англ. data mining)[5].
OCaml, как и другие диалекты ML, используются в исследовательских задачах и задачах верификации, при котором основной код пишется на некотором языке программирования, а затем формально верифицируется и анализируется программой на OCaml[5]. Например, на OCaml написана система интерактивного доказательства теоремCoq.
Занимает особое место среди языков программирования благодаря сочетанию эффективности, выразительности и практичности. Среди особенностей языка, развивавшихся в течение более чем 40 лет, со времени созданияML, выделяют следующие[6]:
- статическая проверка типов,
- параметрический полиморфизм,
- функции первого класса,
- алгебраические типы данных исопоставление с образцом,
- поддержка программирования с неизменяемыми структурами данных (англ. immutable programming),
- автоматическаясборка мусора,
- эффективный компилятор вмашинный код без примененияJIT-компиляции.
История
[править |править код]OCaml ведёт своё происхождение от ML (англ. meta language), который был реализован на диалектеЛиспаРобином Милнером в1972 году в качестве программного средства для доказательства теорем, как метаязык логики вычислимых функций (LCF,англ. logic for computable functions). Позднее был сделанкомпилятор, а к1980 году ML стал полноценной системой программирования[7].
Ги Кузино (Guy Cousineau) добавил в язык алгебраические типы данных и сопоставление с образцом и определил ML в видекатегориальной абстрактной машины (CAM). Таким образом, CAM-ML мог быть описан, верифицирован и оптимизирован, что явилось шагом вперёд для ML[8].
Дальнейшим развитием был созданный к1987 году Аскандером Суарецом (Ascánder Suárez) и продолженный Пьером Вейсом (Pierre Weis) и Мичелом Мони (Michel Mauny) языкCaml (переигранное CAM-ML)[7][8].
В1990 году Ксавье Лерой (Xavier Leroy) и Дамьен Долигез (Damien Doligez) выпустили новую реализацию, названнуюCaml Light. В этой реализации наСи использовалсяинтерпретаторбайт-кода и быстрый сборщик мусора. С написанием библиотек язык стал использоваться в образовании и исследовательских институтах[7][8].
В1995 году увидел светCaml Special Light, развиваемый К. Лероем. Система программирования получила компилятор в машинные коды, что поставило эффективность исполняемого кода в один ряд с другими компилируемыми языками. В то же время была разработанасистема модулей, идея которой была заимствована изStandard ML[7].
В современном виде OCaml появился в1996 году, когда Дидье Реми (Didier Rémy) и Джером Вуйон (Jérôme Vouillon) реализовали для языка стройную и эффективную поддержкуобъектов. Эта объектная система позволяет на этапе компиляции втипобезопасной манере использовать идиомыобъектно-ориентированного программирования, без свойственныхC++ иJava провероквремени выполнения[7].
В2000-х годах язык плавно развивался, одновременно получая всё большее признание в коммерческих проектах и образовании. Среди разработанного в это время можно отметить полиморфные методы и вариантные типы, именованные и необязательные параметры, модулипервого класса,обобщённые алгебраические типы данных (GADT). Язык стал поддерживать несколько аппаратных платформ (X86,ARM,SPARC,PowerPC)[7][8].
Базовая семантика
[править |править код]Модель вычислений OCaml как языка функционального программирования строится на трёх основных конструкцияхлямбда-исчисления: переменных➤, определениях функций➤ и применении функции к аргументам[9].
Переменные
[править |править код]Переменная — идентификатор, значение которого связано с определённой величиной. Имена переменных начинаются со строчной буквы или подчёркивания. Привязка обычно выполняется с помощью ключевого словаlet, как в следующем примере в интерактивной оболочке[10]:
letv=1;;
Переменные имеютобласть видимости. Например, в интерактивной оболочке переменную можно использовать в следующих за её привязкой командах. Аналогично, переменную, определённую в модуле, можно использовать после определения в данном модуле[10].
Привязка переменной может быть осуществлена и в области видимости, заданной конструкцией let-in, как в следующем примере по вычислению площади круга по радиусу:
#letarearadius=letpi=3.14inradius*.radius*.pi;;valarea:float->float=<fun>#area2.0;;-:float=12.56
В OCaml привязки переменных являются неизменяемыми (как в математических уравнениях), то есть, значение переменной «присваивается» только один раз (единичное присваивание). Другое дело, что внутри let-in может быть другой let-in, в котором вводится другая переменная, которая может «затенить» первую[10].
Функции
[править |править код]Для определения функций в OCaml есть несколько синтаксических конструкций.
Функции можно определить с помощью ключевого словаfunction. Выражение для функции выглядит следующим образом[11]:
functionx->x+1
В данном случаефункция анонимная, и её можно использовать в качестве параметров других функций или применить к некоторому аргументу, например:
(functionx->x+1)5
Типом этой функции являетсяint -> int, то есть, функция принимаетцелое и возвращает целое.
Функция может иметь несколько аргументов[12]:
function(x,y)->x-y
В этом примере её тип:int * int -> int, то есть, на входе функции — пара➤, а на выходе — целое.
Есть и другой подход представления функций нескольких аргументов — преобразованиеN-арной функции в N функций одного аргумента —каррирование. Следующие два вида записи функции, вычисляющей произведение целочисленных аргументов, эквивалентны[12]:
functionx->functiony->x*yfunxy->x*y
Именованные функции можно получить, связав переменную с функцией[11]. Определение именованной функции настолько частая операция, что имеет отдельную синтаксическую поддержку. Следующие три записи — эквивалентные способы определить функцию (в интерактивной оболочке):
#letprod=functionx->functiony->x*y;;valprod:int->int->int=<fun>#letprodxy=x*y;;valprod:int->int->int=<fun>#letprod=funxy->x*y;;valprod:int->int->int=<fun>
Функции двух аргументов можно определить для использованияинфиксной записи[11]:
#let(^^)xy=x**2.0+.y**2.0;;val(^^):float->float->float=<fun>#2.0^^3.0;;-:float=13.#(^^)2.03.0;;-:float=13.
В этом примере определена функция(^^), вычисляющая сумму квадратов двухчисел с плавающей запятой. Последние два вида записи эквивалентны.
Рекурсивные функции, то есть функции, ссылающиеся на своё же определение, можно задать с помощьюlet rec[11]:
#letrecfacn=matchnwith|0->1|x->x*fac(x-1);;
В этом же примере вычисленияфакториала применено сопоставление с образцом (конструкцияmatch-with).
Аргументы функции можно определить как именованные. Именованные аргументы можно указывать в любом порядке[11]:
#letdivmod~x~y=(x/y,xmody);;valdivmod:x:int->y:int->int*int=<fun>#divmod~x:4~y:3;;-:int*int=(1,1)#divmod~y:3~x:4;;-:int*int=(1,1)
В OCaml можно опускать значения, используя уплотнённую запись (англ. label punning), если имя параметра и переменной совпадают[11]:
#letx=4inlety=3indivmod~x~y;;-:int*int=(1,1)
Выражения
[править |править код]Ассоциативность операций в выражениях OCaml определяется префиксом, распространяясь таким образом на операции, определённые пользователем. Знак- работает и как префиксная, и как инфиксная операция, причём при необходимости использовать в качестве префикса совместно с применением функции параметр нужно заключить в скобки[13].
| Префикс операции | Ассоциативность |
|---|---|
!?~ | Префикс |
. .( .[ .{ | |
применение функции, конструктора, метки,assert,lazy | Левая |
- -. | Префикс |
** lsl lsr asr | Правая |
* / % mod land lor lxor | Левая |
+ - | Левая |
:: | Правая |
@ ^ | Правая |
| & $ != | Левая |
&&& | Правая |
or|| | Правая |
, | |
<-:= | Правая |
if | |
; | Правая |
letmatchfunfunctiontry |
Система типов
[править |править код]Примитивные типы
[править |править код]Язык OCaml имеет несколькопримитивных типов: числовые типы (целый и числа с плавающей запятой),символьный,строки символов,булевый[14].
Целый тип представляет целые числа из интервала [−230, 230 − 1] и [−262, 262 − 1] для 32- и 64-битных архитектур соответственно. С целыми числами можно производить обычные операции сложения, вычитания, умножения, деления, взятияостатка от деления:+,-,*,/,mod. В случае выхода результата за допустимый интервал ошибки не происходит, а результат вычисляется по модулю границы интервала[15].
Числа с плавающей запятой представляются 53-битноймантиссой и порядком из интервала [−1022, 1023], следуя стандартуIEEE 754 для чисел с двойной точностью. В операциях эти числа нельзя смешивать с целыми. Кроме того, операции над числами с плавающей запятой синтаксически отличаются от целочисленных операций:+.,-.,*.,/.. Также имеется операция возведения в степень:**. Для преобразования целых чисел в числа с плавающей запятой и обратно доступны функции: float_of_int и int_of_float[15].
Для чисел с плавающей запятой имеются и другие математические функции:тригонометрические (sin, cos, tan, asin, acos, atan), округления (ceil, floor), экспоненциальная (exp), логарифмические (log, log10), а также извлечение квадратного корня (sqrt)[15]. Для числовых типов имеются и полиморфные операции сравнения[15].
Символьный тип — char — соответствует представлению символа с кодом от 0 до 255 (первые 128 символов совпадают сASCII).Строчный тип — string — последовательность символов (максимальная длина: 224 — 6)[16]. Пример с использованием функции преобразования целого к строке и операцииконкатенации:
#"Example "^string_of_int(2);;-:string="Example 2"
Булевый тип имеет два значения:true (истина) иfalse (ложь). Операции над величинами булевого типа:унарнаяnot (отрицание), бинарные:&& (и),|| (или). Бинарные операции вычисляют сначала левый аргумент, а правый — только если требуется[17].
Булевые значения получаются в результате сравнений:= (структурное равенство),== (тождество),<> (отрицание структурного равенства),!= (отрицание тождества),<,>,<=,>=. Для примитивных типов кроме строк и чисел с плавающей точкой структурное равенство и тождество совпадают, для других типов тождественными считаются значения, располагающиеся по одному адресу в памяти, а при структурном сравнении значения проверяются покомпонентно[17].
Кроме того, в OCaml имеется специальный тип unit, который имеет всего одно значение —()[17].
Списки
[править |править код]В OCamlсписок — конечная неизменяемая последовательность элементов одного типа, реализованная как односвязный список. Следующий пример демонстрирует синтаксис списка[18]:
#['a';'b';'c'];;-:charlist=['a';'b';'c']#'a'::('b'::('c'::[]));;-:charlist=['a';'b';'c']#'a'::'b'::'c'::[];;-:charlist=['a';'b';'c']#[];;-:'alist=[]
Операция:: позволяет построить список на основе нового элемента и хвоста старого списка. При этом «старый» список не изменяется:
#letlst=[1;2];;vallst:intlist=[1;2]#letlst1=0::lst;;vallst1:intlist=[0;1;2]#lst;;-:intlist=[1;2]#lst1;;-:intlist=[0;1;2]
Пример: вычисление суммы элементов списка
[править |править код]Список является одним из основных типов данных в OCaml. Следующий пример кода определяет рекурсивную (обратите внимание на ключевое слово rec) функцию, которая перебирает элементы данного списка и возвращает их сумму:
letrecsumxs=matchxswith|[]->0|x::xs'->x+sumxs'
# sum [1;2;3;4;5];; - : int = 15
Другой способ подсчёта суммы заключается в использовании функции свёртки:
letsumxs=List.fold_left(+)0xs#sum[1;2;3;4;5];;-:int=15
Записи
[править |править код]Записи являются важным элементом в системе типов OCaml. Запись представляет собой набор хранимых вместе значений, при котором каждый элемент значения-записи доступен по своему имени — имени поля записи. Пример описания типа, связывания записи с переменной и доступ к полю записи[19]:
#typeuser={login:string;password:string;nick:string;};;#letusr={login="myuser";password="secret";nick="aka";};;valusr:user={login="myuser";password="secret";nick="aka"}#usr.nick;;-:string="aka"
Следует заметить, что тип переменной usr был установлен компилятором автоматически.
Как и в случае с другими типами, тип может быть параметризован. Другие возможности записей[19]:
- сопоставление с образцом (irrefutable)
- синтаксические уплотнение полей (field punning) записи в случае совпадения имён полей и переменных
- повторное использование полей и разрешение неоднозначности с помощью модулей
- функциональное обновление (functional update) записи
- изменяемые (mutable) поля
- fieldslib и поле записи какобъект первого класса
- итераторы полей
Вариантный тип
[править |править код]Вариантный тип представляет данные, которые могут принимать различные формы, определяемые явно заданными метками. В следующем примере определён тип для базовых цветов[20]:
#typemain_color=Red|Green|Blue;;#Blue;;-:main_color=Blue#(Red,Blue);;-:main_color*main_color=(Red,Blue)
В примере выше вариантный тип используется в качествеперечислимого типа. В OCaml вариантный тип, тем не менее, является более богатым, так как помимо меток позволяет задавать и данные, например:
#typecolor_scheme=RGBofint*int*int|CMYKoffloat*float*float*float;;typecolor_scheme=RGBofint*int*int|CMYKoffloat*float*float*float
При определении функций вариантный тип естественно сочетается с сопоставлением с образцом.
Объекты
[править |править код]В OCaml объекты и их типы полностью отделены от системыклассов. Классы используются для построения объектов и поддержкинаследования, но не являются типами объектов. Объекты имеют собственныеобъектные типы (object types), и для работы с объектами классы применять необязательно. Объекты не так часто используются в OCaml (так, система модулей является более выразительной, чем объекты, так как модули могут включать типы, а классы и объекты — нет). Основным преимуществом объектов перед записями — они не требуют объявления типов и обладают большей гибкостью благодаря полиморфизму строчных переменных (англ. row polymorphism). С другой стороны, преимущества объектов проявляются при использовании системы классов. В отличие от модулей, классы поддерживают позднее связывание, что позволяет ссылаться на методы объекта без статически заданной реализации и использовать открытую рекурсию (в случае с модулями можно использовать функции и функторы, но синтаксически такие описания требуют написания большего количества кода)[21].
Вывод типов
[править |править код]Хотя OCaml являетсяязыком программирования с сильной типизацией, системавывода типов (англ. type inference) позволяет определять тип выражения на основе имеющейся информации о его компонентах. В следующем примере функции проверки числа на чётность не указано ни одной декларации типа, и тем не менее у компилятора языка есть полная информация о типе функции[22]:
#letoddx=xmod2<>0;;valodd:int->bool=<fun>
Императивное программирование и функции с побочными эффектами
[править |править код]Помимо функциональных, язык содержит средстваимперативного программирования: функции спобочными эффектами, изменяемые (mutable) данные, императивные синтаксические конструкции, в частности, явныециклыwhile иfor[23].
Следующий пример напечатает настандартном выводе (это — побочный эффект функции printf) 11 строк:
fori=0to10doPrintf.printf"i =%d\n"idone;;
В следующем (довольно искусственном) примере элементы массива на месте увеличиваются на единицу в цикле с предусловием. Для индекса массива используется ссылка (ref), которая инкрементируется в теле цикла:
#letincr_arar=leti=ref0inwhile!i<Array.lengthardoar.(!i)<-ar.(!i)+1;incridone;;valincr_ar:intarray->unit=<fun>#letnums=[|1;2;3;4;5|];;valnums:intarray=[|1;2;3;4;5|]#incr_arnums;;-:unit=()#nums;;-:intarray=[|2;3;4;5;6|]
Побочные эффекты позволяют оптимизировать вычисления, в особенности, когда речь идёт о значительных преобразованиях на больших массивах данных. Также с их помощью реализуютсяленивые вычисления имемоизация[23].
Крупномасштабное программирование
[править |править код]Модульность
[править |править код]OCaml можно представить себе как состоящий из двух языков: язык ядра со значениями и типами и язык модулей и ихсигнатур. Эти языки образуют два слоя в том смысле, что модули могут содержать типы и значения, а обычные значения не могут содержать модулей и модулей-типов. Тем не менее, OCaml предлагает механизм модулейпервого класса, которые могут быть значениями и при необходимости преобразуются в обычные модули и обратно[24].
Функторы
[править |править код]Система модулей OCaml не ограничивается модульной организацией кода и интерфейсами. Одними из важных инструментовобобщённого программирования являютсяфункторы. Упрощённо говоря, функторы являются функцией из модуля в модули, что позволяет реализовать следующие механизмы[25]:
- Внедрение зависимости (англ. dependency injection)
- Авторасширение модулей (англ. autoextension of modules)
- Создание экземпляра (англ. instantiation) модуля с некоторым состоянием
Примеры программ
[править |править код]Запуск интерпретатора OCaml
[править |править код]Для запуска интерпретатора языка OCaml необходимо в консоли ввести следующую команду:
$ocamlOCamlversion4.08.1#
Вычисления можно производить в интерактивном режиме, например:
#1+2*3;;-:int=7
Hello world
[править |править код]Следующая программа «hello.ml»:
print_endline"Hello World!";;
может быть скомпилирована либо вбайт-код:
$ocamlchello.ml-ohello
либо в оптимизированныймашинный код:
$ocamlopthello.ml-ohello
и запущена:
$./helloHelloWorld!$
Быстрая сортировка
[править |править код]В следующем примере приведён алгоритмбыстрой сортировки, который сортирует список в порядке возрастания:
letrecqsort=function|[]->[]|pivot::rest->letis_lessx=x<pivotinletleft,right=List.partitionis_lessrestinqsortleft@[pivot]@qsortright
Последовательность Фибоначчи
[править |править код]letrecfib_auxnab=matchnwith|0->a|_->fib_aux(n-1)(a+b)aletfibn=fib_auxn01
См. также
[править |править код]Примечания
[править |править код]- ↑https://ocaml.org/releases/5.4.0
- ↑https://ocaml.org/docs/up-and-running
- ↑A History of OCaml . Дата обращения: 22 апреля 2019. Архивировано 1 сентября 2015 года.
- ↑Minsky, 2011.
- ↑1234Smith, 2006, p. 2—3.
- ↑Minsky, Madhavapeddy, Hickey, 2013, Why OCaml?.
- ↑123456Minsky, Madhavapeddy, Hickey, 2013, A Brief History.
- ↑1234Smith, 2006, p. 3—4.
- ↑Chailloux, Manoury, Pagano — Developing with OCaml, 2007, p. 11—12.
- ↑123Minsky, Madhavapeddy, Hickey, 2013, Variables.
- ↑123456Minsky, Madhavapeddy, Hickey, 2013, Functions.
- ↑12Chailloux, Manoury, Pagano — Developing with OCaml, 2007, p. 23.
- ↑OCaml manual: 6.7 Expressions . Дата обращения: 6 января 2015. Архивировано 1 января 2015 года.
- ↑Chailloux, Manoury, Pagano — Developing with OCaml, 2007, p. 12.
- ↑1234Chailloux, Manoury, Pagano — Developing with OCaml, 2007, p. 13.
- ↑Chailloux, Manoury, Pagano — Developing with OCaml, 2007, p. 15.
- ↑123Chailloux, Manoury, Pagano — Developing with OCaml, 2007, p. 15—16.
- ↑Minsky, Madhavapeddy, Hickey, 2013, List Basics.
- ↑12Minsky, Madhavapeddy, Hickey, 2013, Chapter 5. Records.
- ↑Minsky, Madhavapeddy, Hickey, 2013, Chapter 6. Variants.
- ↑Minsky, Madhavapeddy, Hickey, 2013, Objects.
- ↑Minsky, Madhavapeddy, Hickey, 2013, Functions and Type Inference.
- ↑12Minsky, Madhavapeddy, Hickey, 2013, Imperative Programming.
- ↑Minsky, Madhavapeddy, Hickey, 2013, First-Class Modules.
- ↑Minsky, Madhavapeddy, Hickey, 2013, Functors.
Литература
[править |править код]- Minsky, Y. and Madhavapeddy, A. and Hickey, J. Real World OCaml: Functional Programming for the Masses. — O'Reilly Media, 2013. — 510 p. —ISBN 9781449324766.
- Перевод на русский язык:Мински, Ярон; Мадхавапедди, Анил; Хикки, Джейсон. Программирование на языке OCaml = Real World OCaml: Functional Programming for the Masses. — ДМК, 2014. — 536 с. — (Функциональное программирование). —ISBN 978-5-97060-102-0.
Примечание — в книге используется перевод термина «first-class function» как «функция первого порядка». Но следует иметь в виду, что в многочисленных англоязычных источниках (по семантике языков вообще и поML иХиндли-Милнеру в частности) концептуально различается четыре понятия:
- first-class,
- second-class,
- first-order,
- high-order,
причём «first-class» — это «лучше», чем «second-class» (шире по возможностям, ближе к теории и выше по порогу вхождения (C. Strachey —Fundamental Concepts in Programming Languages)), но «first-order» примитивнее, чем «high-order». В частности,расширение языка модулей ML до уровня«first-class high-order» представляет собой существенно большую проблематику для исследователей, чем его расширение только до «first-class» или только до «high-order» (Rossberg A. Functors and runtime vs compile time . Дата обращения: 25 июня 2015. Архивировано изоригинала 26 июня 2015 года.).
- Joshua B. Smith. Practical OCaml. — Apress, 2006. — 488 с. —ISBN 9781590596203.
- Yaron Minsky. OCaml for the Masses // ACM Queue: Programming Languages. — 2011. —Т. 9,№ 9.
- Chailloux, Manoury, Pagano. Developing Applications With Objective Caml (англ.). — 2007.
- Харрисон Дж. Введение в Функциональное Программирование =http://www.cl.cam.ac.uk/Teaching/Lectures/funprog-jrh-1996/. — 1997. Архивировано изоригинала 11 января 2015 года.
Ссылки
[править |править код]- Официальный сайт языка OCaml (англ.)
- Сайт OCaml Community (англ.)
- Ocamlverse (англ.) Documenting everything about OCaml
- Сайт Gerd Stolpmann’a, посвящённый OCaml (англ.)
- Перевод документации на русский язык (для OCaml v. 3)
- Emmanuel Chailloux, Pascal Manoury and Bruno PaganoDeveloping Applications With Objective Caml (англ.)
- Страницы, использующие устаревший тег source
- Википедия:Cite web (не указан язык)
- Википедия:Статьи со ссылками на элементы Викиданных без русской подписи
- Википедия:Статьи с источниками из Викиданных
- ПРО:ИТ:Статьи по алфавиту
- ПРО:ИТ:Последняя правка: в прошлом году
- Страницы, использующие волшебные ссылки ISBN