2016-03-01 4 views
7

Большинство методов, которые я знаю из списков, на самом деле являются особыми случаями некоторых известных классов типов. Некоторые примеры методов и связанных с классом типа:Что такое обобщение unzip?

  • map :: (a -> b) -> [a] -> [b] и Functor
  • foldr :: (a -> b -> b) -> b -> [a] -> b и Foldable
  • forM :: Monad m => [a] -> (a -> m b) -> m [b] и Traversable
  • concat :: [[a]] -> [a] и Monad

Возможно, этот список можно продолжить (помилования каламбур).

Мне интересно о «более глубоком значении» за unzip :: [(a, b)] -> ([a], [b]). Может ли он быть реализован с использованием некоторых известных экземпляров [] и, например, экземпляра-функтора (,a)? Или какие-то другие случаи? В идеале, я бы хотел иметь более абстрактную функцию с этим типом: SomeClass m => unzip :: m (a, b) -> (m a, m b). Есть ли класс, который сделает эту работу?

+1

[Data.Align.Unalign] (http://hackage.haskell.org/package/these-0.6.2.1/docs/Data-Align.html#t:Unalign) –

+0

Связанный: «Более глубокое значение» _zip_ является [правильным присоединением] (https://hackage.haskell.org/package/adjunctions-4.3/docs/Data-Functor-Adjunction.html). – Turion

ответ

7

Вы можете просто взять первые и вторые выступы:

gunzip :: Functor f => f (a, b) -> (f a, f b) 
gunzip ps = (fmap fst ps, fmap snd ps) 

Но обратите внимание, что gunzip траверсов ps дважды в отличии от обычного zip для списков, так что это хлопотно, потому что ps не мусор после первого прохода и пребывания в памяти, что вызывает утечку памяти в больших списках.

+1

Хороший ответ. Я думаю, стоит также отметить, что 'gunzip', специализированный на' [] ', менее эффективен, чем' unzip' (он принимает два прохода вместо одного). –

+0

@ Крис Тейлор, хороший момент, спасибо. Я отредактирую. – user3237465

+0

Я понимаю, что иногда это может быть быстрее, но утечка памяти укуса. – dfeuer

6

Наглядно, функция unzip' должна разрушить структуру холдинга (a,b), а затем построить его на две копии структуры, первый из которых содержит a и второй из которых содержит b.

Одно из возможных обобщений, уже упоминалось в другом ответе, является

unzip' :: (Functor t) => t (a, b) -> (t a, t b) 
unzip' xs = (fmap fst xs, fmap snd xs) 

Это соответствует счету, но один недостаток заключается в том, что он должен пройти через первоначальную структуру дважды - один раз для каждого вызова fmap.


Foldable класс описывает структуры, которые можно перемещаться, построение результата, как мы идем. Мы можем воспользоваться этим свойством, чтобы гарантировать, что мы пройдем только первоначальную структуру один раз (один вызов foldr), но нам еще нужно знать, как создать копии структур снова.

MonadPlus типа класса обеспечивает способы получить пустую структуру, и в сочетании двух структур (немного как высшего порядка Monoid) -

class Monad m => MonadPlus m where 
    mzero :: m a 
    mplus :: m a -> m a -> m a 

С этим, мы можем написать

import Control.Monad 
import Data.Foldable (foldr) 

unzip' :: (Foldable t, MonadPlus m) => t (a, b) -> (m a, m b) 
unzip' = foldr f (mzero, mzero) 
    where 
    f (a,b) (as, bs) = (mplus (return a) as, mplus (return b) bs) 

Мы можем тогда делать такие вещи, как

>> unzip' [(1,2), (3,4)] :: ([Int], [Int]) 
([1,3],[2,4]) 

но также и

>> unzip' (Right (1,2)) :: ([Int], Maybe Int) 
([1],Just 2) 

Одна последняя мысль - это немного некрасиво, что нам нужно вызвать mplus (return a) as. Возможно, было бы лучше иметь такой класс, как

class Listish m where 
    null :: m a 
    cons :: a -> m a -> m a 

, но я еще не изучил этот фрагмент проектного пространства.

+1

Но 'unzip''" линеаризует "структуру: распаковка дерева не приводит к кортежу деревьев, а скорее в кортежей скрытых списков. – user3237465

+0

@ user3237465 Если дерево является правильно определенным экземпляром 'Functor', то' fmap' будет генерировать дерево, а не список. Тип 'fmap' -' a -> b -> f a -> f b', а не 'a -> b -> f a -> [b]'. – chepner

+0

@chepner, я имел в виду второй 'unzip '= foldr f'. – user3237465

Смежные вопросы