Как и другие предлагали, вероятно, разумно начать с понимания списка:
-- | Repeat each element of a list twice.
double :: [x] -> [x]
double xs = [d | x <- xs, d <- [x, x]]
Но тот факт, что второй список в понимании всегда имеет одинаковое количество элементов, независимо от значенияx
означает, что нам не нужно так много энергии: достаточно интерфейса Applicative
.Давайте начнем с написания понимания немного по-другому:
double xs = xs >>= \x -> [x, x] >>= \d -> pure d
Мы можем сразу упростить, используя закон тождества монад:
double xs = xs >>= \x -> [x, x]
Теперь мы переключимся на Applicative
, но давайте оставимотверстие для жесткой части:
double :: [x] -> [x]
double xs = liftA2 _1 xs [False, True]
Компилятор сообщает нам, что
_1 :: x -> Bool -> x
Поскольку элементы внутреннего / второго списка всегда одинаковы и всегда происходят изтекущий внешний / первый элемент списка, нам не нужно заботиться о Bool
:
double xs = liftA2 const xs [False, True]
Действительно, нам даже не нужно быть способным , чтобы различать списокпозиции:
double xs = liftA2 const xs [(),()]
Конечно, у нас есть специальный Applicative
метод (<*)
, который соответствует liftA2 const
, поэтому давайте использовать его:
double xs = xs <* [(),()]
И затемЕсли хотите, мы можем избежать упоминания xs
, переключившись на форму «без точек»:
-- | Repeat each element of a list twice.
double :: [x] -> [x]
double = (<* [(),()])
Теперь для теста:
main :: IO ()
main = print $ double [1..3]
Это напечатает [1,1,2,2,3,3]
.
double
допускает небольшое обобщение сомнительного значения:
double :: Alternative f => f x -> f x
double = (<* join (<|>) (pure ()))
Это будет работать и для последовательностей:s списки:
double (Data.Sequence.fromList [1..3]) = Data.Sequence.fromList [1,1,2,2,3,3]
, но это может быть немного запутанным для некоторых других Alternative
экземпляров:
double (Just 3) = Just 3