Ответ 1
Вы можете использовать этот комбинатор: (Двоеточие подсказывает, что следуют два аргумента)
(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(.:) = (.) . (.)
Это позволяет вам избавиться от n
:
time = concat .: replicate
Как объединить с помощью стиля $
и point-free?
Ясным примером является следующая функция утилиты:
times :: Int -> [a] -> [a]
times n xs = concat $ replicate n xs
Просто запись concat $ replicate
вызывает ошибку, аналогично вы не можете писать concat . replicate
либо потому, что concat
ожидает значение, а не функцию.
Итак, как бы вы превратили указанную выше функцию в бесконтактный стиль?
Вы можете использовать этот комбинатор: (Двоеточие подсказывает, что следуют два аргумента)
(.:) :: (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)
Любой инфиксный оператор - это просто хорошая функция ol:
2 + 3 == (+) 2 3
f 2 3 = 2 `f` 3
A оператор компоновки - это тоже двоичная функция, более высокий, закажите один, он принимает 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 может быть частично применена из-за currying по умолчанию. Операторы Infix могут быть частично применены очень кратко, используя разделы :
(-) == (\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
¹ Haskell основан на теории категорий. Категория в теории категорий состоит из трех вещей: некоторые объекты, некоторые морфизмы и понятие композиции морфизмов. Каждый морфизм связывает исходный объект с целевым объектом в одностороннем порядке. Теория категорий требует, чтобы композиция морфизмов была ассоциативной. Категория, которая используется в 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
(.:::) = (.).(.::)
...
Очень приятно.
Bonus
(.:::) :: (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