Список исполнителей

Это может применяться для любого типа класса, но позволяет делать это для функторов, поскольку я их знаю лучше. Я не буду создавать этот список.

l = [Just 1, [1,2,3], Nothing, Right 4]

а затем

map (fmap (+1)) l

чтобы получить

[Just 2, [2,3,4], Nothing, Right 5]

Я знаю, что они - все Фунтеры, которые содержат Интов, так что это возможно. Как я могу это сделать?

Edit

Это оказывается более грязным, чем казалось бы. В Java или С# вы объявите интерфейс IFunctor, а затем просто напишите

List<IFunctor> l = new List<IFunctor> () {
    new Just (1),
    new List<Int>() {1,2,3},
    new Nothing<Int>(),
    new Right (5)
}

предполагая Maybe, List и Either реализовать IFunctor. Естественно Just и Nothing расширяют Maybe и Right и Left расширяют Either. Не удовлетворены этой проблемой, которую легче решить на этих языках!!!

В Haskell должен быть более чистый путь:(

Ответы

Ответ 1

В Haskell подавление не допускается. Вы можете использовать AnyFunctor, но проблема в том, что больше нет способа вернуться к функтору, который вам известен. Когда у вас есть AnyFunctor a, все, что вы знаете, это то, что у вас есть f a для некоторого f, поэтому все, что вы можете сделать, это fmap (получение другого AnyFunctor). Таким образом, AnyFunctor a фактически эквивалентно ().

Вы можете добавить структуру в AnyFunctor, чтобы сделать ее более полезной, и мы увидим ее немного позже.

Сопротивление фукторов

Но во-первых, я поделюсь тем, что, вероятно, в конечном итоге сделаю это в реальной программе: с помощью комбинаторов функций.

{-# LANGUAGE TypeOperators #-}

infixl 1 :+:   -- declare this to be a left-associative operator

data (f :+: g) a = FLeft (f a) | FRight (g a)
instance (Functor f, Functor g) => Functor (f :+: g) where
    -- left as an exercise

По мере чтения типа данных f :+: g является функтором, значения которого могут быть либо f a, либо g a.

Затем вы можете использовать, например:

l :: [ (Maybe :+: []) Int ]
l = [ FLeft (Just 1), FRight [2,3,4], FLeft Nothing ]

И вы можете наблюдать по шаблону:

getMaybe :: (Maybe :+: g) a -> Maybe a
getMaybe (FLeft v) = v
getMaybe (FRight _) = Nothing

Он становится уродливым, когда вы добавляете больше функторов:

l :: [ (Maybe :+: [] :+: Either Int) Int ]
l = [ FLeft (FLeft Nothing), FRight (Right 42) ]
-- Remember that we declared :+: left-associative.

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

Вы можете сделать условия немного более чистыми, создав явный союз, как рекомендует Ganesh:

data MyFunctors a
    = FMaybe (Maybe a)
    | FList [a]
    | FEitherInt (Either Int a)
    | ...

Но вы платите, переустанавливая Functor для него ({-# LANGUAGE DeriveFunctor #-} может помочь). Я предпочитаю мириться с уродством и работать на достаточно высоком уровне абстракции, где он не становится слишком уродливым (т.е. Как только вы начнете писать FLeft (FLeft ...) время рефакторинга и обобщения).

Coproduct можно найти в пакете comonad-transformers, если вы не хотите его реализовывать самостоятельно (хотя это хорошее упражнение). Другие общие комбинаторы функций находятся в пространстве имен Data.Functor. в пакете transformers.

Экзистенции с понижающим потоком

AnyFunctor также может быть расширен, чтобы обеспечить понижение. Включение downcasting должно быть явно включено добавлением класса Typeable к тому, что вы намерены преуменьшить. Каждый конкретный тип является экземпляром Typeable; конструкторы типов являются экземплярами Typeable1 (1 аргумент); и т.д. Но это не распространяется на переменные типа, поэтому вам нужно добавить ограничения класса. Таким образом, решение AnyFunctor становится:

{-# LANGUAGE GADTs #-}

import Data.Typeable

data AnyFunctor a where
    AnyFunctor :: (Functor f, Typeable1 f) => f a -> AnyFunctor a

instance Functor AnyFunctor where
    fmap f (AnyFunctor v) = AnyFunctor (fmap f v)

Что позволяет downcasting:

downcast :: (Typeable1 f, Typeable a) => AnyFunctor a -> Maybe (f a)
downcast (AnyFunctor f) = cast f

Это решение на самом деле чище, чем я ожидал, и может стоить того.

Ответ 2

Один из подходов заключается в использовании экзистенциальных:

{-# LANGUAGE GADTs #-}
data AnyFunctor v where
    AnyFunctor :: Functor f => f v -> AnyFunctor v

instance Functor AnyFunctor where
    fmap f (AnyFunctor fv) = AnyFunctor (fmap f fv)

Список входных данных, который вы запрашиваете в своем вопросе, невозможен, поскольку он не правильно напечатан, поэтому может потребоваться некоторая упаковка, например AnyFunctor, но вы подходите к ней.

Вы можете сделать список входных данных, обернув каждое значение в конструкторе данных AnyFunctor:

[AnyFunctor (Just 1), AnyFunctor [1,2,3],
 AnyFunctor Nothing, AnyFunctor (Right 4)]

Обратите внимание, что при использовании fmap (+1) рекомендуется использовать явную подпись типа для 1, чтобы избежать проблем с числовой перегрузкой, например. fmap (+(1::Integer)).

Трудность с AnyFunctor v в ее нынешнем виде состоит в том, что вы не можете на самом деле многое с этим справиться - вы даже не можете смотреть на результаты, потому что это не экземпляр Show, не говоря уже об извлечении значения для будущего использования.

Немного сложно сделать это в примере Show. Если мы добавим ограничение Show (f v) к конструктору данных AnyFunctor, то экземпляр Functor перестанет работать, потому что нет гарантии, что он создаст экземпляр Show. Вместо этого нам нужно использовать своего рода "более высокий порядок" typeclass Show1, как обсуждалось в этом ответе:

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE GADTs #-}

data AnyFunctor v where
    AnyFunctor :: (Show1 f, Functor f) => f v -> AnyFunctor v

instance Functor AnyFunctor where
    fmap f (AnyFunctor fv) = AnyFunctor (fmap f fv)

data ShowDict a where
    ShowDict :: Show a => ShowDict a

class Show1 a where
    show1Dict :: ShowDict b -> ShowDict (a b)

instance Show v => Show (AnyFunctor v) where
    show (AnyFunctor (v :: f v)) =
        case show1Dict ShowDict :: ShowDict (f v) of
           ShowDict -> "AnyFunctor (" ++ show v ++ ")"

instance Show1 [] where
    show1Dict ShowDict = ShowDict

instance Show1 Maybe where
    show1Dict ShowDict = ShowDict

instance Show a => Show1 (Either a) where
    show1Dict ShowDict = ShowDict

В ghci это дает следующее (я сломал строки для удобочитаемости):

*Main> map (fmap (+1)) [AnyFunctor (Just 1), AnyFunctor [1,2,3],
                          AnyFunctor Nothing, AnyFunctor (Right 4)]

[AnyFunctor (Just 2),AnyFunctor ([2,3,4]),
 AnyFunctor (Nothing),AnyFunctor (Right 5)]

Основная идея состоит в том, чтобы выразить идею о том, что конструктор типа Nothing, [] или Either a "сохраняет" ограничение Show, используя класс Show1, чтобы сказать, что Show (f v) доступен когда Show v доступен.

Тот же трюк применяется к другим классам. Например, ответ @luqui показывает, как вы можете извлекать значения с помощью класса Typeable, который уже имеет встроенный вариант Typeable1. Каждый класс класса, который вы добавляете, ограничивает все, что вы можете поместить в AnyFunctor, но также означает, что вы можете делать с ним больше.

Ответ 3

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

Другим было бы создание специализированных кортежей * -> * как:

newtype FTuple4 fa fb fc fd r = FTuple4 (fa r, fb r, fc r, fd r)
  deriving (Eq, Ord, Show)

Таким образом, кортеж является однородным по значениям, но гетерогенным по функторам. Затем вы можете определить

instance (Functor fa, Functor fb, Functor fc, Functor fd) =>
         Functor (FTuple4 fa fb fc fd) where
    fmap f (FTuple4 (a, b, c, d)) =
        FTuple4 (fmap f a, fmap f b, fmap f c, fmap f d)

и

main = let ft = FTuple4 (Just 1,
                         [1,2,3],
                         Nothing,
                         Right 4 :: Either String Int)
       in print $ fmap (+ 1) ft

При таком подходе вы можете легко сопоставить соответствие результатов, не теряя информацию о типах отдельных элементов, их порядках и т.д. И вы можете иметь похожие экземпляры для Foldable, Traversable, Applicative и др.

Также вам не нужно реализовывать экземпляр Functor самостоятельно, вы можете использовать GHC, производящие расширения, поэтому все, что вам нужно написать для получения всех экземпляров просто

{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}

import Data.Foldable
import Data.Traversable

newtype FTuple4 fa fb fc fd r = FTuple4 (fa r, fb r, fc r, fd r)
  deriving (Eq, Ord, Show, Functor, Foldable, Traversable)

И даже это может быть дополнительно автоматизировано для произвольной длины, используя Template Haskell.

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


Другой альтернативой без собственного типа данных будет использование вложенных продуктов functor, поскольку то, что вы описываете, - это всего лишь продукт из 4 функторов.

import Data.Functor.Product

main = let ft = Pair (Just 1)
                  (Pair [1,2,3]
                    (Pair Nothing
                         (Right 4 :: Either String Int)
                  ))
           (Pair a (Pair b (Pair c d))) = fmap (+ 1) ft
        in print (a, b, c, d)

Это несколько многословно, но вы можете сделать гораздо лучше, создав свой собственный продукт-функтор с помощью операторов типов:

{-# LANGUAGE TypeOperators, DeriveFunctor #-}

data (f :*: g) a = f a :*: g a
  deriving (Eq, Ord, Show, Functor)
infixl 1 :*:

main = let a :*: b :*: c :*: d = fmap (+ 1) $ Just 1 :*:
                                              [1,2,3] :*:
                                              Nothing :*:
                                              (Right 4 :: Either String Int)
        in print (a, b, c, d)

Это становится возможно как можно более кратким и универсальным.