2014-10-14 8 views

ответ

6

Как правило: если переменная появляется в выражении более одного раза, , вероятно, это не очень хорошая идея, чтобы сделать ее бесконтактной.. Однако, если вы определились, наименее нечитаемый способ заключается в комбинаторах Arrow, потому что это делает довольно понятным, где поток данных «разделен». Для xs я бы написал

uncurry (zipWith (...)) . (id &&& inits) 

Для x, тот же метод дает

zipWith (curry $ uncurry(,) . (fst &&& length . uncurry filter . first(>))) 

Это даже больше, чем -monad решение (->), что вы использовали и lambdabot наводит на мысль, но это выглядит гораздо более организованный.

8

Это возможно, но абсолютно не стоит боль. Чтобы получить ответ на свой вопрос, LambdaBot на FreeNode сообщает:

f = flip (zipWith (liftM2 (.) (,) ((length .) . filter . flip (<)))) =<< inits 

На данный момент функция потеряла то, что ясность он имел, и стал неосновательный. Здесь вам гораздо лучше представить настоящие имена. Помните, только потому, что мы можем сделать вещи точными, это не значит, что мы должны.

3

Не сказать, что я рекомендую, но король Pointfree является Control.Arrow

import Control.Arrow 

-- A special version of zipWith' more amenable to pointfree style 
zipWith' :: ((a, b) -> c) -> ([a], [b]) -> [c] 
zipWith' = uncurry . zipWith . curry 

f :: Ord a => [a] -> [(a, Int)] 
f = zipWith' (fst &&& (length <<< uncurry filter <<< first (>))) <<< id &&& inits 

Позвольте мне reclarify здесь-я действительно не рекомендую это, если ваши намерения не является каким-то обобщать вид стрелки ваша программа (например, в стрелковом FRP).

+1

с 'zipWith '= uncurry. zipWith. карри' –

+0

Ха, да, отредактирован. –

2

С хорошо известного

(f .: g) x y = f (g x y) 

это пол-читаемым

zipWith (curry (fst &&& uncurry (length .: (filter . flip (<))))) <*> inits 
--  \(x,ys) -> (x ,   length ((filter . flip (<)) x ys)) 

Используя Control.Applicative (f <*> g $ x = f x (g x), то S комбинатор) и Control.Arrow (как и другие, но немного бит по-разному).

+0

Является ли это '.:' Достаточно известным, чтобы быть частью любого стандартного пакета? – Bergi

+0

@ Bergi Я не уверен. Вы можете попробовать Hoogle для своего типа. Там есть [tag: dot-operator], или что-то еще, на SO тоже. –

+0

А, я только что нашел ['Data.Composition'] (http://hackage.haskell.org/package/composition-1.0.1.0/docs/Data-Composition.html) (по совпадению, Hoogle не нашел'. : ') – Bergi

5

Точка стиля pointfree не только опуская имена для значений, но предпочитая имена функций. Это значительно легче сделать, когда вы используете очень маленькие определения. Конечно, любой код станет нечитаемым, если вы включите все и не будете использовать хорошие имена.

Итак, давайте начнем с вашей первоначальной функции и разделим ее на несколько меньших определений.

f xs = zipWith combine xs (inits xs) 
combine x xs = (x, countWhere (< x) xs) 
countWhere f xs = length (filter f xs) 

Теперь мы можем легко сделать эти определения точными в удобочитаемом виде.

f = zipWith combine <*> inits 
    where combine = compose (,) countLessThan 

compose = liftA2 (.) 
countLessThan = countWhere . flip (<) 
countWhere = length .: filter 
(.:) = (.) . (.) 

Использование имен рассудительно и предпочитая состав над приложением позволяет учитывать код на мелкие, легко понимаемых определений. Именованные параметры эквивалентны goto для данных мощных, но лучше всего используются для создания многократно используемых структур более высокого уровня, которые легче понять и использовать правильно. Эти составные комбинаторы, такие как (.) и <*>, предназначены для передачи данных, которые map, filter и fold предназначены для управления потоком.

+0

меня лично , Я быстро потерялся в этом лесу имен. Для меня точка-точка, не нужно называть то, что не важно. Не только аргументы, но и промежуточные функции - * в основном * временные функции. –

+0

@WillNess: Да, я думаю, вы должны указывать имена в основном на функции, которые вы ожидаете повторного использования, но вы также будете использовать их больше, если они доступны, потому что вы их назвали, так что это компромисс. –

4

Мой удар в нем:

f :: Ord a => [a] -> [(a, Int)] 
f = zip <*> ((zipWith $ (length .) . filter . (>)) <*> inits) 

Здесь я заменил (<) с (>) иметь (length .) . filter . (>) как функцию с аргументами в правильном порядке: a->[a]->Int. Передавая его zipWith, мы получаем [a]->[[a]]->[Int].

Предположим, что мы имеем [a] на входе, мы можем видеть это как f ([[a]]->[Int]) для Applicative ((->) [a]), которые могут быть объединены с inits :: f [[a]] с <*> :: f ([[a]]->[Int])->f [[a]]->f [Int]. Это дает нам [a]->[Int], теперь необходимо одновременно использовать как [a], так и [Int]. zip уже имеет подходящий тип: [a]->[Int]->[(a,Int)], чтобы подать заявку с <*>.

+0

ах, приятно. это завершает линию мысли * follow-the-data * (re: double-'<*>'). Таким образом, нет никакого беспорядка с 'curry' /' uncurry'. –

+0

@WillNess yes, сначала я написал 'zip <*> ...', но использовал '(= <<)' вместо '<*>' с 'inits'. Не упоминая монаду, он выглядит почти как рифма о «читателе». –

+1

@WillNess :) просто слишком взволнован, как аккуратно это выглядит. Это было отличное упражнение для меня, не привык смотреть на такие функции. –

 Смежные вопросы

  • Нет связанных вопросов^_^