Бессмысленный стиль и использование $
Как сочетать использование $
а бессмысленный стиль?
Наглядным примером является следующая служебная функция:
times :: Int -> [a] -> [a]
times n xs = concat $ replicate n xs
Просто пишу concat $ replicate
выдает ошибку, аналогично вы не можете написать concat . replicate
либо потому что concat
ожидает значение, а не функцию.
Так как бы вы превратили вышеуказанную функцию в стиль без точек?
5 ответов
Вы можете использовать этот комбинатор: (двоеточие подсказывает, что следуют два аргумента)
(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(.:) = (.) . (.)
Это позволяет избавиться от n
:
time = concat .: replicate
Вы можете легко написать почти бессмысленную версию с
times n = concat . replicate n
Полностью бессмысленная версия может быть достигнута с явным карри и некурри:
times = curry $ concat . uncurry replicate
Получить на freenode и спросить lambdabot;)
<jleedev> @pl \n xs -> concat $ replicate n xs
<lambdabot> (join .) . replicate
В Haskell композиция функций ассоциативна¹:
f . g . h == (f . g) . h == f . (g . h)
Любой инфиксный оператор - это просто хорошая старая функция:
2 + 3 == (+) 2 3
f 2 3 = 2 `f` 3
Оператор композиции также является бинарной функцией, более высокого порядка, он принимает 2 функции и возвращает функцию:
(.) :: (b -> c) -> (a -> b) -> (a -> c)
Поэтому любой оператор композиции может быть переписан так:
f . g == (.) f g
f . g . h == (f . g) . h == ((.) f g) . h == (.) ((.) f g) h
f . g . h == f . (g . h) == f . ((.) g h) == (.) f ((.) g h)
Каждая функция в Haskell может быть применена частично из-за карри по умолчанию. Инфиксные операторы могут быть частично применены очень кратким способом, используя разделы:
(-) == (\x y -> x - y)
(2-) == (-) 2 == (\y -> 2 - y)
(-2) == flip (-) 2 == (\x -> (-) x 2) == (\x -> x - 2)
(2-) 3 == -1
(-2) 3 == 1
Так как оператор композиции - это обычная бинарная функция, вы можете использовать ее и в разделах:
f . g == (.) f g == (f.) g == (.g) f
Другой интересный бинарный оператор - это $, который является просто приложением функции:
f x == f $ x
f x y z == (((f x) y) z) == f x y z
f(g(h x)) == f $ g $ h $ x == f . g . h $ x == (f . g . h) x
С этим знанием, как я могу преобразовать concat $ replicate n xs
в бессмысленный стиль?
times n xs = concat $ replicate n xs
times n xs = concat $ (replicate n) xs
times n xs = concat $ replicate n $ xs
times n xs = concat . replicate n $ xs
times n = concat . replicate n
times n = (.) concat (replicate n)
times n = (concat.) (replicate n) -- concat is 1st arg to (.)
times n = (concat.) $ replicate n
times n = (concat.) . replicate $ n
times = (concat.) . replicate
¹ Хаскель основан на теории категорий. Категория в теории категорий состоит из 3 вещей: некоторые объекты, некоторые морфизмы и понятие композиции морфизмов. Каждый морфизм соединяет исходный объект с целевым объектом односторонним образом. Теория категорий требует, чтобы композиция морфизмов была ассоциативной. Категория, которая используется в Haskell, называется Hask, чьи объекты являются типами, а морфизмы - функциями. Функция f :: Int -> String
это морфизм, который связывает объект Int
для объекта String
, Поэтому теория категорий требует, чтобы композиции функций Хаскелла были ассоциативными.
Расширяя ответ FUZxxl, мы получили
(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(.:) = (.).(.)
(.::) :: (d -> e) -> (a -> b -> c -> d) -> a -> b -> c -> e
(.::) = (.).(.:)
(.:::) :: (e -> f) -> (a -> b -> c -> d -> e) -> a -> b -> c -> d -> f
(.:::) = (.).(.::)
...
Очень хорошо.
бонус
(.:::) :: (e -> f) -> (a -> b -> c -> d -> e) -> a -> b -> c -> d -> f
(.:::) = (.:).(.:)
Эмм... так может быть, мы должны сказать
(.1) = .
(.2) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(.2) = (.1).(.1)
(.3) :: (d -> e) -> (a -> b -> c -> d) -> a -> b -> c -> e
(.3) = (.1).(.2)
-- alternatively, (.3) = (.2).(.1)
(.4) :: (e -> f) -> (a -> b -> c -> d -> e) -> a -> b -> c -> d -> f
(.4) = (.1).(.3)
-- alternative 1 -- (.4) = (.2).(.2)
-- alternative 2 -- (.4) = (.3).(.1)
Даже лучше.
Мы также можем расширить это до
fmap2 :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b)
fmap2 f = fmap (fmap f)
fmap4 :: (Functor f, Functor g, Functor h, functro i)
=> (a -> b) -> f (g (h (i a))) -> f (g (h (i b)))
fmap4 f = fmap2 (fmap2 f)
который следует той же схеме.
Было бы еще лучше иметь время подачи заявок fmap
или же (.)
параметризованных. Тем не менее, те fmap
или же (.)
на самом деле разные по типу. Таким образом, единственный способ сделать это, например, использовать расчет времени компиляции TemplateHaskell
,
Для повседневного использования я бы просто предложил
Prelude> ((.).(.)) concat replicate 5 [1,2]
[1,2,1,2,1,2,1,2,1,2]
Prelude> ((.).(.).(.)) (*10) foldr (+) 3 [2,1]
60