Большое спасибо archimag,
откликнувшегося на мою предложение написать пост.
Мне стало понятно практически всё, чего я не смог вытащить по кусочкам из комментариев.
Хочется, однако, поднять тему сравнения дизайнов программ. Особенно, ещё не написанных программ. А потом я про типы в дизайне расскажу, куда ж без них. ;)
Насколько я понял, позиция archimag (как же адресовать blogspot?) такова: в самом начале мы не можем сказать, насколько хорош дизайн, мы это можем сказать только апостериори.
Если программа может быть написана двумя или более способами, то необходимо произвести выбор одного из них. Archimag предлагает выбрать дизайн наугад и потом оценить, подошёл ли он. Желательно бы делать лучше, например, уметь сравнивать дизайны между собой.
В том самом обсуждении я предложил следующий вариант: сперва сравниваем дизайны по тому, как много сценариев программы они покрывают, потом сравниваем по количеству кода, потребному для реализации дизайна. Такая несложная иерархия.
Если включить в рассмотрение важность сценариев и сложность их реализации и отсортировать их, как это рекомендует
gaperton, то получится хорошая система оценки дизайна программы, ещё более иерархичная, быстрее отсеивающая относительно плохие дизайны.
Теперь надо немного добавить про типы.
В обсуждении прозвучала мысль, что типы языка Хаскель основываются на логике, по-моему, на классическом её варианте (могу быть неправ, но в прилагательном;). Выражая типы компонент и пробуя прикинуть приблизительную их реализацию Хаскеле мы проверяем наш дизайн с помощью системы автоматического доказательства теорем. Это помогает отсевать те варианты дизайна, что не могут быть логически стройно сформулированы.
Типы в дизайне не мешают построить расширяемый дизайн: для расширяемости тип компоненты должен быть полиморфным по одному или больше параметрам. Поведение полиморфных частей компоненты мы можем фиксировать с помощью типов классов, если это требуется. Если нам требуется несколько вариантов поведения, мы можем использовать многопараметрические типы классов, где один из параметров фиксирует определённый вариант поведения.
Приведу пример.
В том посте я упоминал, что dup и over не должны уметь работать, когда на вершине стека составной тип. Сами dup и over полностью полиморфны, для них нет ограничений, но в реальной системе они есть.
Для ограничения действий dup и over мы введём парочку классов:
-- у класса FAIL не должно быть реализаций. Сейчас покажу, почему.
class FAIL e
-- класс StackSlot указывает, что тип может быть сохранён в ячейке стека.
class StackSlot a
-- и реализации, который просто сигнализируют, что можно использовать
-- как элемент стека.
instance StackSlot Int
instance StackSlot Char
instance StackSlot (Pointer a)
-- А вот для Maybe потребуется FAIL, реализации которого быть не может.
instance FAIL (Maybe a) => StackSlot (Maybe a)
-- Наши новые типы для dup и over:
dup :: StackSlot a => StackOp (Stack1 stk a) (Stack2 stk a a)
over :: (StackSlot a, StackSlot b) =>
StackOp (Stack2 stk b a) (Stack3 stk b a b)
Теперь наши примитивы более безопасны и почти так же удобны.
Типы дают более масштабируемый вариант программы - проще увеличить количество людей, ведь контракты-то зафиксированы! С масштабируемостью по железу сложней, тут я не знаю, как действовать.
Хотя идея приблизительно понятна. Это уже проходили в
"Зависимых типах для распределённых массивов" - надо задать типами контракты на содержимое каждого узла системы, тогда будет понятно, что куда как идёт.
Пожалуй, это всё, что я хотел сказать. ;)
PS
Вспомнил.
Ещё типы позволяют лучше управлять повторным использованием кода. Вот.
Проще отыскать что с чем и как работает.
Что должно положительно сказываться на скорости внесения изменений.