Подтвердить что ты не робот

Как, почему и когда использовать шаблон ".Internal"?

Я видел пару пакетов на hackage, которые содержат имена модулей с .Internal в качестве своего последнего компонента (например, Data.ByteString.Internal)

Эти модули, как правило, не могут быть просмотрены в браузере (но они все равно могут отображаться) в Haddock и не должны использоваться клиентским кодом, но содержат определения, которые либо реэкспортируются из открытых модулей, либо просто используются внутри.

Теперь мой вопрос к этому шаблону организации библиотеки:

  • Какие проблемы решаются этими модулями .Internal?
  • Есть ли другие предпочтительные способы решения этих проблем?
  • Какие определения следует перенести в те модули .Internal?
  • Какая рекомендуемая практика в отношении организации библиотек с помощью таких модулей .Internal?
4b9b3361

Ответ 1

Internal модули, как правило, представляют собой модули, которые выставляют внутренние компоненты пакета, которые разрушают инкапсуляцию пакетов.

Чтобы взять ByteString в качестве примера: когда вы обычно используете ByteString s, они используются как непрозрачные типы данных; Значение a ByteString является атомарным, и его представление неинтересно. Все функции из Data.ByteString принимают значения ByteString и никогда не сырые Ptr CChar или что-то еще.

Это хорошо. это означает, что авторам ByteString удалось сделать представление достаточно абстрактным, чтобы все детали о ByteString могли быть полностью скрыты от пользователя. Такая конструкция приводит к инкапсуляции функциональности.

Модули Internal предназначены для людей, которые хотят работать с внутренними элементами инкапсулированной концепции, чтобы расширить инкапсуляцию.

Например, вы можете создать новый тип данных BitString, и вы хотите, чтобы пользователи могли конвертировать ByteString в BitString без копирования какой-либо памяти. Чтобы сделать это, вы не можете использовать opaque ByteString s, потому что это не дает вам доступ к памяти, которая представляет ByteString. Вам нужен доступ к необработанному указателю памяти к байтовым данным. Это то, что обеспечивает модуль Internal для ByteString.

Затем вы должны сделать свой тип данных BitString инкапсулированным, тем самым расширяя инкапсуляцию, не нарушая ее. Затем вы можете предоставить свой собственный модуль BitString.Internal, подвергая внутренности вашего типа данных пользователям, которые могут захотеть проверить его представление по очереди.

Если кто-то не предоставляет модуль Internal (или аналогичный), вы не можете получить доступ к внутреннему представлению модуля, а пользовательская запись, например. BitString вынужден (ab) использовать такие вещи, как unsafeCoerce, чтобы накладывать указатели на память, и все становится некрасиво.

Определения, которые должны быть помещены в модуль Internal, являются фактическими объявлениями данных для ваших типов данных:

module Bla.Internal where

data Bla = Blu Int | Bli String

-- ...

module Bla (Bla, makeBla) where -- ONLY export the Bla type, not the constructors

import Bla.Internal

makeBla :: String -> Bla -- Some function only dealing with the opaque type
makeBla = undefined

Ответ 2

@dflemstr прав, но не явным образом о следующем. Некоторые авторы помещают внутреннюю часть пакета в модуль .Internal, а затем не выставляют этот модуль через cabal, тем самым делая его недоступным для клиентского кода. Это плохая вещь 1.

Открытые модули .Internal помогают сообщать различные уровни абстракции, реализованные модулем. Альтернативы:

  • Экспортировать детали реализации в том же модуле, что и абстракция.
  • Скрыть детали реализации, не подвергая их экспорту модулей или через cabal.

(1) делает документацию запутанной и затрудняет пользователю рассказывать о переходе между его кодом, относящимся к абстракции модуля и его разбиению. Этот переход важен: он аналогичен удалению параметра функции и замене ее вхождений на константу, потерю общности.

(2) делает вышеуказанный переход невозможным, а препятствует повторному использованию кода. Мы хотели бы сделать наш код как можно более абстрактным, но (см. Einstein) не более того, и у автора модуля не так много информации, как у пользователя модуля, поэтому он не в состоянии решить, какой код должен быть недоступным, См. Ссылку больше для этого аргумента, так как это несколько своеобразно и противоречиво.

Экспозиция модулей .Internal обеспечивает счастливую среду, которая связывает барьер абстракции, не применяя ее, позволяя пользователям легко ограничиться абстрактным кодом, но позволяя им "бета-расширять" модуль, если абстракция ломается или неполна.


1 Есть, конечно, осложнения для этого пуристского суждения. Внутреннее изменение теперь может разорвать клиентский код, и теперь у авторов больше обязательств по стабилизации их реализации, а также их интерфейса. Даже если он должным образом отвергнут, пользователи являются пользователями и получают поддержку, поэтому есть призыв к скрытию внутренних компонентов. Он запрашивает политику пользовательской версии, которая различает .Internal и изменения интерфейса, но, к счастью, это согласуется с (но не явным) политикой управления версиями . "Реальный код" также известен как ленивый, поэтому разоблачение модуля .Internal может обеспечить простоту, когда существует абстрактный способ определения кода, который был просто "сложнее" (но в конечном итоге поддерживает повторное использование сообщества). Он также может препятствовать отчетности об упущении в абстрактном интерфейсе, который действительно должен быть отброшен автору для исправления.

Ответ 3

Идея состоит в том, что вы можете иметь "правильный", стабильный API, который вы экспортируете из MyModule, и это предпочтительный и документированный способ использования библиотеки.

В дополнение к общедоступному API ваш модуль, вероятно, имеет частные конструкторы данных и внутренние вспомогательные функции и т.д. Подмодуль MyModule.Internal может использоваться для экспорта этих внутренних функций, а не для их полной блокировки внутри модуля.

  • Это позволяет пользователям вашей библиотеки получать доступ к внутренним компонентам, если они нуждаются в том, чего вы не ожидали, но при том понимании, что они обращаются к внутреннему API, который не имеет таких же скрытых гарантий, как и общедоступный.
  • Он позволяет вам обращаться к внутренним функциям и конструкторам, например. единицы тестирования.

Ответ 4

Одно расширение (или, возможно, разъяснение) на то, что сказал shang и dflemstr: если у вас есть внутренние определения (типы данных, конструкторы которых не экспортируются и т.д.), которые вы хотите получить из нескольких экспортируемых модулей, тогда вы обычно создайте такой модуль .Internal, который вообще не отображается (т.е. указан в Other-Modules в файле .cabal).

Однако иногда это происходит при выполнении типов в ghci (например, при использовании функции, но где некоторые из типов, к которым она относится, не входят в область видимости), не может думать о экземпляре, где это происходит в верхней части моя голова, но это так).