В качестве языка примера буду использовать PHP , для простоты. Поясню сразу суть вопроса. В современных языках, особенно это касается интерпритируемых, есть типизация традиционно не строгая:
function join($a){
return is_array($a) ? implode('', $a) : $a;
}
А есть типизация строгая:
function join(array $a) : string{
return implode('', $a);
}
Есть ещё, например в C++ шаблонная типизация:
template
Есть ещё в 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)
Комментариев нет:
Отправить комментарий