Как обращаться с символами infix, выглядящими уродливыми с квалифицированными именами

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

Однако мне кажется, что нужно разместить пространство имен на символе инфикса (или других коротких идентификаторах DSL-y), выглядит действительно странно, поэтому я испытываю соблазн переэкспортировать значения, например:

import qualified Data.Sequence as Seq
(|>) = (Seq.|>)
(<|) = (Seq.<|)

Что меня беспокоит, так это то, что

  • Вручную реэкспортные значения выглядят как скучный шаблон.

  • Значения, связанные с переэкспортированием вручную, проходят вокруг существующей системы модулей и, похоже, не работают с конструкторами данных (и, возможно, с другими вещами, с которыми я еще не сталкивался)

    import qualified Data.Sequence as Seq
    (:>) = (Seq.:>)  --gives me a parse error:
                     --"Not in scope: data constructor `:>'"
    

Как скомбинировать символы infix и пространство имен? Должен ли я просто сдаваться и учиться пространству имен? Установлены ли наилучшие практики Haskell в отношении пространств имен и символов?

Ответ 1

Ну, одна вещь, которую вы можете сделать, это импортировать дважды:

import Data.Sequence ((|>), (<|), ViewR ((:>)))
import qualified Data.Sequence as Seq

Это будет импортировать только :>, |> и <| неквалифицированные, оставив все остальное квалифицированным. Обратите внимание, что поскольку :> является конструктором данных, вам также необходимо импортировать его тип данных (ViewR), но вам не нужно импортировать остальные конструкторы ViewR.

Кроме того, если вы беспокоитесь о конфликтах, вам следует просто скрыть оператора:

import Prelude hiding ((.))

Если вы используете разумную библиотеку, конфликт с Prelude означает, что функция библиотеки предназначена для замены этой функции Prelude (например, Control.Category), поэтому вы хотите, чтобы она заменила значение по умолчанию.

Что касается лучших практик, я никогда не видел, чтобы кто-либо использовал квалифицированных операторов, если не существует конфликта или они находятся в GHCi. Все сказанное, даже факторинга в пользу того, зная, откуда находится оператор, делает код гораздо менее удобочитаемым.

Ответ 2

Я вообще импортирую имена типов, конструкторы и операторы неквалифицированные, а все остальные квалифицированные:

import Data.Sequence (Seq, ViewL(..), ViewR(..), (|>), (<|))
import qualified Data.Sequence as Seq

Этот стиль с двойным импортом, неквалифицированным типом рекомендуется документацией для Data.Map и других стандартных контейнеров.

Тем не менее, вы не можете всегда импортировать операторы неквалифицированными - например, если вы используете Array/Vector и Map в том же модуле, вы не можете импортировать (!) из неквалифицированных, В этом случае я обычно использовал бы его квалифицированный. Это выглядит странно, но это лучше, чем другие варианты (например, придумать свое имя для одного из них, чтобы избежать столкновения). Конечно, возможно, это хорошо, если он останавливает людей, использующих небезопасные функции, такие как (Data.Map.!):)