Удаление конструктора newtype

Чаще всего я пишу функции, которые лишают единственный конструктор нового типа, например, в следующей функции для возврата первого аргумента, который не является Nothing:

process (Pick xs) = (\(First x) -> x) . mconcat . map (First . process) $ xs

Я думаю, что лямбда излишне многословна. Я хотел бы написать что-то вроде этого:

process (Pick xs) = -First . mconcat . map (First . process) $ xs

Допускают ли возможности метапрограммирования Haskell что-то подобное? Любое другое решение для решения этой проблемы более лаконичным способом также приветствуется.

УПД. Запрошен весь код:

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

person NioBium    schedule 05.05.2015    source источник
comment
Похоже на coerce.   -  person Zeta    schedule 05.05.2015
comment
Каким должен быть тип process? Можно использовать пакет newtype, чтобы скрыть большую часть этого. Все, что я могу сделать, это то, что Pick должен принадлежать к рекурсивному типу, поскольку Pick :: [a] -> PickType и process :: PickType -> Maybe a, но First . process :: PickType -> First a, значит, xs :: [PickType]?   -  person bheklilr    schedule 05.05.2015
comment
Это просто игрушечный пример, но я добавлю его в ОП.   -  person NioBium    schedule 05.05.2015


Ответы (4)



В этом случае вы можете использовать пакет 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"
person bheklilr    schedule 05.05.2015

Если вы используете Data.Monoid.First, то это просто getFirst. Многие оболочки нового типа используют синтаксис записи, чтобы предоставить простую функцию для развертывания нового типа.

person Louis Wasserman    schedule 05.05.2015

Метапрограммирование выглядит слишком сложным для этого. я бы просто использовал

unFirst (First x) = x  -- define once, use many times

process (Pick xs) = unFirst . mconcat . map (First . process) $ xs

Часто бывает так, что функция определяется вместе с новым типом, например.

newtype First a = First { unFirst :: a }
person chi    schedule 05.05.2015
comment
Спасибо. Это лучше, но если мне нужно написать много новых типов, было бы неплохо иметь что-то вроде того, что я просил. Несмотря на то, что на данный момент кажется, что записи — это лучшее, что я мог сделать, у меня есть личное предубеждение против них (в этой форме они также кажутся избыточными и повторяющимися; даже вы сделали ошибку, добавив префикс имени конструктора с и un, а не получить, и, на мой взгляд, должна существовать единая трактовка этого случая). - person NioBium; 05.05.2015
comment
@NioBium Единственный текущий унифицированный способ, как вы пишете, - это (\(First x)->x), который громоздкий, но не такой длинный. Однако я согласен с тем, что было бы неплохо иметь более прямой унифицированный способ. В библиотеках вы также найдете run- в качестве общего префикса для обратной операции, по крайней мере, в контексте монадических новых типов. - person chi; 05.05.2015