2015-05-05 5 views
7

Чаще всего я пишу функции, обнажая единственный конструктор нового типа, например, в следующей функции, чтобы вернуть первый аргумент, что не ничего:Зачистка конструктор NewType

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

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

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 
+0

Похоже на «принуждение». – Zeta

+0

Каким должен быть тип 'process'? Возможно, можно использовать пакет 'newtype', чтобы скрыть большую часть этого. Все, что я могу сделать, это то, что 'Pick' должен принадлежать рекурсивному типу, так как' Pick :: [a] -> PickType' и 'process :: PickType -> Maybe a', но' First. process :: PickType -> Сначала a', поэтому 'xs :: [PickType]'? – bheklilr

+0

Это просто игрушечный пример, но я добавлю его в OP. – NioBium

ответ

3

Как Зет предложил в комментариях, 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 
4

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

4

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

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

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

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

newtype First a = First { unFirst :: a } 
+0

Спасибо. Это лучше, но если мне нужно написать много новых типов, было бы неплохо иметь что-то, как то, что я просил. Несмотря на то, что на данный момент кажется, что записи - это лучшее, что я мог бы сделать, у меня есть личные предубеждения против них (в этой форме они также кажутся излишними и повторяющимися, даже вы допустили ошибку при префиксах имени конструктора и «un», в отличие от «получить», и, на мой взгляд, должно существовать единообразное отношение к этому делу). – NioBium

+0

@NioBium Единственный точный равномерный способ, как вы пишете, - '(\ (First x) -> x)', который является громоздким, но не настолько длинным. Однако я согласен с тем, что иметь более прямой единообразный способ может быть приятным. В библиотеках вы также найдете 'run-' как общий префикс для обратной операции, по крайней мере в контексте монадических newtypes. – chi

5

В этом случае вы можете использовать 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" 
Смежные вопросы