Как мне обойти Go, не имея параметрического полиморфизма?
Я новичок, но я читал, что регулярные люди Go не пропустят параметрический полиморфизм. Каждый раз, когда я пытаюсь изучить новый язык, я использую список проблем L99, чтобы получить некоторую практику.
Даже если я попытаюсь написать что-то столь же тривиальное, как первая проблема (которая в Go будет единственным оператором, взяв последний элемент среза), как бы я написал это как функцию, которая принимает кусочек любого типа и (используя этот единственный оператор, на который я ссылался выше) возвращает последний элемент этого среза?
Я понял, что даже если язык не имеет параметрического полиморфизма, должен быть какой-то идиоматический способ "Go" сделать это, чтобы регулярные члены Go требовали, чтобы они не пропустили параметрический полиморфизм. В противном случае, если пример был более сложным, чем только последний элемент списка, например, вам понадобится функция для выполнения вашей задачи для каждого типа.
Что мне не хватает?
Ответы
Ответ 1
Вы ссылаетесь на проблемы "99 lisp", но lisp вообще не имеет параметрического полиморфизма или статических типов.
Многие статически типизированные языки, такие как Objective-C и Java до дженериков, не имеют параметрического полиморфизма. Решение состоит в том, чтобы просто использовать тип, который может принимать все значения, которые в Go interface{}
, и бросать, когда вам нужно получить какой-то конкретный тип.
Для вашего конкретного вопроса, как взять "любой тип среза"; к сожалению, нет интерфейса, который включает в себя специфические срезы, так как срезы не имеют никаких методов; поэтому вы будете придерживаться с помощью interface{}
. Поскольку у вас есть неизвестный тип среза, вам нужно использовать отражение (пакет reflect
) для выполнения всех операций среза, включая получение длины и емкости, добавление и доступ к элементу по определенному индексу.
Другой альтернативой является то, что вместо использования "кусочка любого типа" просто используйте "срез интерфейса {}", т.е. []interface{}
, во всем своем коде, тогда вы можете использовать на нем обычные операторы среза, и вы можете вставляйте любые элементы, но бросайте их, когда вы их вынимаете.
Ответ 2
Способ Go, как вернуть последний элемент среза, - просто написать его inline как выражение. Например:
var a []int
...
last := a[len(a)-1]
Инкапсуляция простого выражения a[len(a)-1]
в общую функцию является ненужным усложнением.
В отличие от Lisp, Go не является чисто функциональным языком. Оценка Go, основанная на списке 99 Lisp, может обманывать. Go - это "язык системного программирования" - манипулирование списками, метапрограммирование, символический AI или другие Lisp - заданные задачи не являются сильными сторонами.
Я рассматриваю Go как улучшенный C с сборкой мусора и concurrency. Go здесь не для того, чтобы конкурировать с Lisp.
Ответ 3
Это очень похоже на то, когда я обнаружил, что несколько раз писал один и тот же код для разных массивов разных типов на других языках программирования, таких как C, fpc или delphi. Я изобрел параметрический полиморфизм для языка, который, вероятно, никогда не будет реализован, с использованием препроцессорных трюков и назвал его "включать параметрический полиморфизм файлов" в качестве доказательства концепции, что вы действительно можете реализовать параметрический полиморфизм на процедурный язык, не требуя ООП или какого-либо сложного система дженериков. Использование препроцессора - это форма злоупотребления, но это было просто доказательство концепции с FPC.
Поскольку Golang не использует препроцессор, вам придется использовать интерфейсы или указатели и отправить тип в качестве параметра. Но даже использование указателей по-прежнему означает, что вам нужно написать много кода, чтобы бросить его и заставить все это работать. Интерфейсы лучше, чем указатели, потому что указатели менее безопасны.
Решения, подобные этому:
last := a[len(a)-1]
Являются склонными к ошибкам, потому что кто-то может забыть минус 1. Некоторые языки имеют что-то немного лучше:
// return last element, the "high" of a
last := a[high(a)]
// return first element, the "low" of a
first := a[low(a)]
Выше код не работает в Go AFAIK (не исследовал, имеет ли go что-то похожее на это), это то, что некоторые другие языки имеют (fpc), которые могут быть чем-то, что считает Go.
Этот низкий и высокий способ борьбы с вещами абсолютно гарантирует выбор последнего и первого элементов, тогда как использование "минус один" склонно к созданию основных математических ошибок. Кто-то может забыть минус один... потому что они запутались в массиве на основе 1 байт по сравнению с нулевыми массивами. Даже если на языке нет такой вещи, как массив, основанный на 1, все равно можно сделать ошибку из-за того, что люди иногда думают по-одному (наши пальцы начинаются с 1, а не 0). Некоторые умные программисты утверждают, что нет, наши пальцы начинаются с нуля, а не одни. Ваш большой палец равен нулю. Хорошо, хорошо.. но.. для большей части мира...;-) мы заканчиваем тем, что переводим назад и вперед наши мозги от 1 до 0 на весь день в реальном мире против компьютерного мира, и это вызывает многочисленные ошибок в программном обеспечении.
Но некоторые утверждают, что "Низкий" и "Высокий" - это просто синтаксический сахар, который не нужен на минимальном языке. Необходимо решить, стоит ли дополнительная безопасность, во многих случаях это может быть. Насколько сложна LOW() и HIGH() добавляет компилятор, я не уверен, и как это влияет на производительность.. Я не уверен на 100 процентов... Я думаю, что компилятор может быть умным в оптимизации высоких и низких, но я не уверен.