Объектно-ориентированное программирование
Несмотря на то, что Ficus в первую очередь функциональный язык, он включает некоторые элементы объектно-ориентированного программирования, способные упростить разработку повторно используемых компонентов и сделать нотацию более удобной. Интерфейсы, представленные в следующем разделе, представляют собой эффективную альтернативу замыканиям, что иногда бывает полезно.
Класс в Ficus определяется почти так же, как запись или вариант:
class [optional_type_parameters] class_name
[ : list_of_implemented_interfaces ]
{
члены класса
}
// или
class [optional_type_parameters] class_name
[ : list_of_implemented_interfaces ] =
| Tag1 [: T1]
| Tag2 [: T2]
...
Основные отличия заключаются в следующем:
- Используется ключевое слово
classвместоtype; - За именем класса может идти список реализуемых интерфейсов;
- Символ
=перед фигурной скобкой опущен.
Вот пример класса прямоугольника и его использования:
class 't Rect
{
x: int
y: int
width: int
height: int
}
// основной конструктор; имя произвольное
fun new_rect(x: 't, y: 't, w: 't, h: 't)
Rect {x=x, y=y, width=w, height=h}
// дополнительный конструктор
fun new_square(x: 't, y: 't, size: 't) =
Rect {x=x, y=y, width=size, height=size}
fun Rect.area() = self.width*self.height
fun Rect.contains((pt_x, pt_y): ('t, 't)) =
self.x <= pt_x < self.x + self.width &&
self.y <= pt_y < self.y + self.height
// переопределим преобразование в строку
fun string(r: Rect) =
f"rect {{ x={r.x}, y={r.y}, width={r.width}, height = {r.height} }}"
// создаём экземпляр типа 'int Rect';
// экземпляр создаётся автоматически
val r = new_rect(5, 9, 10, 10)
val pt = (12, 12)
// используем некоторые методы
println(f"площадь прямоугольника={r.area()},\nсодержит точку {pt}?: {r.contains(pt)}")
Обратите внимание на детали примера:
- Классы, даже обобщённые, определяются как и другте типы Ficus.
-
Класс автоматически получает конструктор записи или вариантный конструктор. Можно определить один или несколько альтернативных конструкторов. Например, если вы реализуете класс нейронной сети, основной конструктор мог бы инициализировать её как пустую модель, а другой конструктор мог бы загружать модель из файла.
- Методы объявляются так же, как обычные функции, имя класса предшествует названию метода. Методы получают доступ к членам класса с помощью нотации
self.member. - Методы объявляются вне тела класса, но должны находиться в том же модуле.
В стандартной библиотеке есть несколько классов для файлов, структур данных и т.д..
Вот пример чтения текстового файла и сбора статистики по самым употребительным словам в тексте:
import Hashmap
import Sys
import File
// создаём экземпляр класса Hashmap.t: ключ=string, данные=int
val wordmap = Hashmap.empty(1024, "", 0)
val separators = " \t,.:;?!()[]{}=><+-/*\\'\"|\n"
val (f, isstdin) = match Sys.arguments() {
| fname :: _ => (File.open(fname, "rt"), false)
| _ => (File.stdin, true)
}
try {
// читаем данные из параметра командной строки
// или из stdin, если не указана команда
while !f.eof() {
val s = f.readln()
// извлекаем слова из строки
val words = s.tokens(fun (c) {separators.contains(c)})
// обновляем карту частот
for w <- words {
val idx = wordmap.find_idx_or_insert(w)
wordmap.table[idx].data += 1
}
}
val n0 = wordmap.size()
val n = min(n0, 30)
val header = f"Самые частые {n} слов в файле:"
print(header + "\n" + "-"*length(header) + "\n")
// извлекаем статистику
val wpairs = wordmap.list()
// сортируем слова по частоте убывания
val sorted_wpairs = wpairs.sort(
fun ((w1, n1): (string, int), (w2, n2): (string, int))
{n1 > n2 || n1 == n2 && w1 < w2})
for (w, count)@i <- sorted_wpairs {
println(f"{w}: {count}")
if i+1 >= n {break}
}
} finally {
if !isstdin {f.close()}
}
Как видно, это не объектно-ориентированная программа, но она в работе использует классы File.t и Hashmap.t.