Непосредственный стиль и использование $

Как объединить с помощью стиля $ и point-free?

Ясным примером является следующая функция утилиты:

times :: Int -> [a] -> [a]
times n xs = concat $ replicate n xs  

Просто запись concat $ replicate вызывает ошибку, аналогично вы не можете писать concat . replicate либо потому, что concat ожидает значение, а не функцию.

Итак, как бы вы превратили указанную выше функцию в бесконтактный стиль?

Ответы

Ответ 1

Вы можете использовать этот комбинатор: (Двоеточие подсказывает, что следуют два аргумента)

(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(.:) = (.) . (.)

Это позволяет вам избавиться от n:

time = concat .: replicate

Ответ 2

Вы можете легко написать почти беспутную версию с помощью

times n  =  concat . replicate n

Полная точечная версия может быть достигнута с явным карри и нерегулярностью:

times  =  curry $ concat . uncurry replicate

Ответ 3

Получить на freenode и спросить lambdabot;)

<jleedev> @pl \n xs -> concat $ replicate n xs
<lambdabot> (join .) . replicate

Ответ 4

В 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. Поэтому теория категорий требует, чтобы композиционные функции Хаскеля были ассоциативными.

Ответ 5

Расширив ответ 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