Страницы

Поиск по вопросам

суббота, 13 октября 2018 г.

Типизация - языки со “множественной типизацией” (union types) аргументов методов?

В качестве языка примера буду использовать PHP , для простоты. Поясню сразу суть вопроса. В современных языках, особенно это касается интерпритируемых, есть типизация традиционно не строгая:
function join($a){ return is_array($a) ? implode('', $a) : $a; }
А есть типизация строгая:
function join(array $a) : string{ return implode('', $a); }
Есть ещё, например в C++ шаблонная типизация:
template R* join(T * array, int count) { R *output = new R(count); for (int i = 0; i < count; i++) output += array[i] ; return output; }
Есть ещё в C++ перегрузка по типам, но она весьма не гибка и громоздка, как и шаблонная типизация, последняя вдобавок ещё и не читаема.
Но очень не хватает типизации множественной (как я узнал - это называется union types) - хотелось бы следующее:
function join(array|Iterator|ICollectable $input) : string|null{ /*какой-то код обработки*/ }
Вопрос - есть ли языки, поддерживающие подобный синтаксис ограничения по типам, если да, то какие? Если нет, то каковы причины, чем концепция плоха?
Почему возникает вопрос отсутствия встроенной поддержки сего - программисты языков с variance-типами, обычно пишут в коментах метода что-то вроде:
/** * Some function describe * * @param array|ISomeList $list ... * @return false|string ... */
Но в объявлении ф-ии они вынуждены не делать явной отсылки к типу(полагаясь на комментарии), оставляя variance типы:
public function someFunction($list) {... return $some;};
Это встречается регулярно в более чем серьёзных фреймворках, которые не могут принебречь читаемостью кода ради строгой типизации.
В итоге при исследовании кода без явного указания желаемых типов: понижается читаемость. Комменты не дают 100% гарантии ограничения типов: программист, как и IDE не может точно знать - что может придти во входном параметре. А при работе программы - если вдруг во входной параметр попало не то что нужно: не происходит ошибок/предупреждений , что усложняет ловлю ошибок, понижает стабильность.
Наоборот же при использовании только строгих типов - безальтернативно усложняется вся система в целом: количество классов растёт геометрично. Что ещё хуже, ведь НЕ-строгость типов работает на простые архитектурные решения и читаемый код, и в случае скриптовых языков ей нельзя принебрегать.
UPD Да, конечно такая фича не совсем ложится на принципы ООП. Но это было бы поводом эти принципы пересматривать - т.к. они очень обходчиво не учитывают "геометрический рост количества классов" . А также union-типизация работает совместно с утиной типизацией - что я бы минусом не назвал, но это большое поле для споров.
UPD2 Если возникает вопрос обоснованности мульти-типов: Более живой пример из PHP - представлено объявление важной, часто используемой функции выборщика объектов из БД-таблиц (db-table-маппера) Zend фреймворка (1.9) класс Zend_Db_Table_Abstract .
/** * Fetches all rows. * * Honors the Zend_Db_Adapter fetch mode. * * @param string|array|Zend_Db_Table_Select $where OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object. * @param string|array $order OPTIONAL An SQL ORDER clause. * @param int $count OPTIONAL An SQL LIMIT count. * @param int $offset OPTIONAL An SQL LIMIT offset. * @return Zend_Db_Table_Rowset_Abstract The row results per the Zend_Db_Adapter fetch mode. */ public function fetchAll($where = null, $order = null, $count = null, $offset = null) { if (!($where instanceof Zend_Db_Table_Select)) { $select = $this->select();
if ($where !== null) { $this->_where($select, $where); }
if ($order !== null) { $this->_order($select, $order); }
if ($count !== null || $offset !== null) { $select->limit($count, $offset); }
} else { $select = $where; }
$rows = $this->_fetch($select);
$data = array( 'table' => $this, 'data' => $rows, 'readOnly' => $select->isReadOnly(), 'rowClass' => $this->getRowClass(), 'stored' => true );
$rowsetClass = $this->getRowsetClass(); if (!class_exists($rowsetClass)) { Zend_Loader::loadClass($rowsetClass); } return new $rowsetClass($data); }
Первый аргумент по замыслу разработчика может иметь один из нескольких типов. Это нужно, чтобы потенциальному пользователю метода(программисту) было очень просто уместить в голове маппер. Ведь он может вызвать из кода этот метод по-разному, но метод при этом остаётся тот-же:
$table->fetchAll('user_id in (1,2,5)'); $table->fetchAll(['user_id < 100', 'gender = "male"']); $table->fetchAll( $table->select()->where('user_id IN (?)', $userIdsArray) );
Один метод легко запомнить, это хорошо ложится в голову. В то время, как программисту метода fetchAll не хочется размазывать логику одной задачи (выбора объектов из базы данных) на несколько перегруженных методов внутри себя - он хочет чтобы всё было в одном месте, чтобы открывший код понял сразу что и в каком порядке работает. Как минимум это соответствует SRP - обязанность у метода одна: выбрать из БД объекты.
То есть предоставляется целый язык для общения с БД-таблицей. Это больше сервис для программиста, нежели просто метод. И такие методы во фреймворках: каждый второй, потому что фреймворк должен быть прежде всего удобным программисту. Программист не должен вникать в "тысячи классов" - чтобы пользоватся фичей - вот это яркий пример решения вопроса читаемости кода, в котором пригодились бы union types.
UPD3 Оказывается разработчики PHP, как подсказали в коментах, тоже думали ввести эту концепцию в 2015 году, но на голосовании - отклонили.


Ответ

Интересный вопрос. Не знаю как в других языках обстоят дела, но в scala то что вы хотите сделать, решается так:
Использованием pattern matching
def f1(obj: Any) = obj match { case x: Array[String] => println("is array") case x: String => println("is string") case _ => println("") }
Так же, есть возможность определить какие методы должны быть у типа аргумента. Например.
case class A() { def m1() = "m1 is invoked" def m2() = println("m2 is invoked") }
def f2(obj: {def m1(): String}) = { println(obj.m1()) }
val obj = A() f2(obj)

Комментариев нет:

Отправить комментарий