Список исполнителей
Это может применяться для любого типа класса, но позволяет делать это для функторов, поскольку я их знаю лучше. Я не буду создавать этот список.
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)
Это становится возможно как можно более кратким и универсальным.