Зачистки конструктор типа
Чаще всего я пишу функции, которые лишают единственный конструктор нового типа, например, в следующей функции, чтобы вернуть первый аргумент, который не является ничем:
process (Pick xs) = ((First x) -> x) . mconcat . map (First . process) $ xs
Я думаю, что лямбда излишне многословна. Я хотел бы написать что-то вроде этого:
process (Pick xs) = -First . mconcat . map (First . process) $ xs
Позволяют ли средства метапрограммирования Haskell что-либо подобное этому? Любое другое решение для решения этой проблемы в более сжатом виде также приветствуется.
UPD. То весь код был запрошен:
data Node where
Join :: [Node] -> Node
Pick :: [Node] -> Node
Given :: Maybe String -> Node
Name :: String -> Node
process :: Node -> Maybe String
process (Join xs) = liftM os_path_join (mapM process xs)
process (Pick xs) = getFirst . mconcat . map (First . process) $ xs
process (Name x) = Just x
process (Given x) = x
4 ответа:
Как предложил Дзета в комментариях,
coerce
это хороший, общий способ сделать это:process (Pick xs) = coerce . mconcat . map (First . process) $ xs
Еще одна приятная вещь о
coerce
заключается в том, что вы можете использовать его для принудительного использования" внутри " конструктора типа без затрат времени выполнения, например:example :: [Sum Int] -> [Int] example = coerce
Альтернатива,
map getFirst
, вызвала бы накладные расходы времени выполнения для обходаmap
.Кроме того, каждый раз, когда вы создаете
newtype
, GHC автоматически создает соответствующий экземплярCoercible
, поэтому вам никогда не придется беспокоиться о том, чтобы возиться с базовый механизм (вам даже не нужноderiving
для него):newtype Test = Test Char example2 :: Maybe Test -> Maybe Char example2 = coerce
В этом случае вы можете использовать пакет
newtypes
для решения этой проблемы более обобщенно:process :: Node -> Maybe String process (Pick xs) = ala' First foldMap process xs process (Join xs) = liftM os_path_join (mapM process xs) process (Name x) = Just x process (Given x) = x
Вы можете даже иметь более общую версию, которая принимает
Newtype n (Maybe String)
какprocess' :: (Newtype n (Maybe String), Monoid n) => (Maybe String -> n) -> Node -> Maybe String process' wrapper (Pick xs) = ala' wrapper foldMap (process' wrapper) xs process' wrapper (Join xs) = liftM os_path_join (mapM (process' wrapper) xs) process' wrapper (Name x) = Just x process' wrapper (Given x) = x
Затем
> let processFirst = process' First > let processLast = process' Last > let input = Pick [Given Nothing, Name "bar", Given (Just "foo"), Given Nothing] > processFirst input Just "bar" > ProcessLast input Just "foo"
В качестве объяснения того, как это работает, функция
ala'
принимает оболочку newtype, чтобы определить экземплярNewtype
для использования, функцию, которой в этом случае мы хотим бытьfoldMap
:foldMap :: (Monoid m, Foldable t) => (a -> m) -> t a -> m
Поскольку
foldMap f
в конечном итоге является обобщеннымmconcat . map f
надFoldable
типами вместо того, чтобы просто перечислять, затем функция, которую можно использовать в качестве "препроцессора" для подключения к переданной функции более высокого порядкаala'
(foldMap
), затем в этом случае некоторыеFoldable t => t Node
обработать. Если вам не нужен шаг предварительной обработки, вы просто используетеala
, который используетid
для своего препроцессора. Использование этой функции иногда может быть затруднено из-за ее сложного типа, но, как показывают примеры в документации,foldMap
часто является хорошим выбором.Сила этого в том, если бы вы захотели написать ваша собственная
newtype
обертка дляMaybe String
:newtype FirstAsCaps = FirstAsCaps { getFirstAsCaps :: Maybe String } firstAsCaps :: Maybe String -> FirstAsCaps firstAsCaps = FirstAsCaps . fmap (fmap toUpper) instance Monoid FirstAsCaps where mempty = firstAsCaps Nothing mappend (FirstAsCaps f) (FirstAsCaps g) = FirstAsCaps $ ala First (uncurry . on (<>)) (f, g) instance Newtype FirstAsCaps (Maybe String) where pack = firstAsCaps unpack = getFirstAsCaps
Затем
> process' firstAsCaps input Just "BAR"
Если вы используете
Data.Monoid.First
, то это простоgetFirst
. Многие обертки newtype используют синтаксис записи, чтобы обеспечить простую функцию для разворачивания newtype.
Мета-Программирование выглядит слишком сложным для этого. Я бы просто использовал
unFirst (First x) = x -- define once, use many times process (Pick xs) = unFirst . mconcat . map (First . process) $ xs
Часто бывает так, что функция определяется вместе с типом newtype, например
newtype First a = First { unFirst :: a }