Я думаю о способах использовать систему типов Haskell для осуществления модульного принципа в программе. Например, если у меня есть веб-приложение, мне любопытно, если существует способ разделить весь код базы данных от кода CGI из кода файловой системы из чистого кода.
Например, я предполагаю монаду DB, таким образом, я мог записать функции как:
countOfUsers :: DB Int
countOfUsers = select "count(*) from users"
Я хотел бы, чтобы это было невозможно использовать побочные эффекты кроме поддерживаемых монадой DB. Я изображаю высокоуровневую монаду, которая была бы ограничена прямыми обработчиками URL и сможет составить вызовы к монаде DB и монаде IO.
Действительно ли это возможно? Действительно ли это мудро?
Обновление: Я закончил тем, что достиг этого с Scala вместо Haskell: http://moreindirection.blogspot.com/2011/08/implicit-environment-pattern.html
Я представляю себе монаду более высокого уровня, которая будет ограничена прямыми обработчиками URL и сможет компоновать вызовы монады DB и монады IO.
Вы, конечно, можете добиться этого и получить очень сильные статические гарантии разделения компонентов.
В самом простом случае вам нужна ограниченная монада IO. Используя что-то вроде техники "запятнания", вы можете создать набор операций ввода-вывода, поднятых в простую обертку, а затем использовать систему модулей для скрытия базовых конструкторов для типов.
Таким образом, вы сможете выполнять код CGI только в контексте CGI, а код DB - только в контексте DB. На Hackage есть много примеров.
Другой способ - создать интерпретатор для действий, а затем использовать конструкторы данных для описания каждой примитивной операции, которую вы хотите. Операции по-прежнему должны образовывать монаду, и вы можете использовать do-notation, но вместо этого вы будете создавать структуру данных, описывающую действия, которые нужно выполнить, и которые вы затем выполняете контролируемым образом через интерпретатор.
Это дает вам, возможно, больше интроспекции, чем нужно в типичных случаях, но этот подход дает вам полную возможность исследовать пользовательский код перед его выполнением.
Спасибо за этот вопрос!
Я работал над клиент-серверным веб-фреймворком, который использовал монады для различения различных сред выполнения. Очевидными были client-side и server-side, но это также позволяло писать both-side код (который мог работать как на клиенте, так и на сервере, поскольку не содержал никаких специальных возможностей), а также asynchronous client-side, который использовался для написания неблокирующего кода на клиенте (по сути, монада продолжения на стороне клиента). Это звучит довольно близко к вашей идее различать код CGI и код DB.
Вот некоторые ресурсы о моем проекте:
Я думаю, это интересный подход, и он может дать вам интересные гарантии о коде. Однако есть несколько непростых вопросов. Если у вас есть серверная функция, которая принимает int
и возвращает int
, то каким должен быть тип этой функции? В своем проекте я использовал int -> int server
(но можно также использовать server (int -> int)
).
Если у вас есть несколько функций, подобных этой, то компоновать их не так просто. Вместо того, чтобы написать goo (foo (bar 1))
, вам нужно написать следующий код:
do b <- bar 1
f <- foo b
return goo f
Вы можете написать то же самое, используя некоторые комбинаторы, но я имею в виду, что композиция немного менее элегантна.