2016-11-10 8 views
7

Есть ли синтаксический сахар «do notation» для простой функциональной композиции?Функция Композиция Обозначение

(т.е. (.) :: (b -> c) -> (a -> b) -> a -> c)

Я хотел бы иметь возможность хранить результаты некоторых композиций для последующего использования (в то же время продолжая цепочку.

Я предпочел бы не использовать расширение RebindableSyntax, если это возможно.

Я ищу что-то вроде этого:

composed :: [String] -> [String] 
composed = do 
    fmap (++ "!!!") 
    maxLength <- maximum . fmap length 
    filter ((== maxLength) . length) 

composed ["alice", "bob", "david"] 
-- outputs: ["alice!!!", "david!!!"] 

Я не уверен, что-то, как это возможно, так как рес ult более ранней функции по существу должен пройти «через» привязку maxLength, но я открыт для слушания любых других аналогичных выразительных вариантов. В основном мне нужно собирать информацию, когда я просматриваю композицию, чтобы использовать ее позже.

Возможно, я мог бы сделать что-то подобное с государственной монадой?

Благодарим за помощь!

Редактировать

Такого рода вещи вроде работает:

split :: (a -> b) -> (b -> a -> c) -> a -> c 
split ab bac a = bac (ab a) a 

composed :: [String] -> [String] 
composed = do 
    fmap (++ "!!!") 
    split 
     (maximum . fmap length) 
     (\maxLength -> (filter ((== maxLength) . length))) 
+0

Пожалуйста, сформулируйте состав вы на самом деле пытаетесь получить здесь. По крайней мере, дайте подпись типа, которую вы хотите для 'compos'! – leftaroundabout

+0

Просто нормальный состав функции, но с возможностью хранения результатов межстраничного доступа для использования в последующих вызовах функций (см. Пример). Я добавил тип. –

+1

Есть ли причина, по которой вам нужно написать ее в стиле без очков? Это должно быть легко в стиле без точек. – immibis

ответ

2

Как leftaroundabout упоминалось, вы можете использовать Arrows, чтобы написать функцию. Но есть функция в ghc Компилятор Haskell, который является proc -notation for Arrows. Он очень похож на известный do -нотацию, но, к сожалению, мало кто знает об этом.

С proc -notation вы можете написать нужную функцию в следующей более redable и элегантным способом:

{-# LANGUAGE Arrows #-} 

import Control.Arrow (returnA) 
import Data.List  (maximum) 

composed :: [String] -> [String] 
composed = proc l -> do 
    bangedL <- fmap (++"!!!")  -< l 
    maxLen <- maximum . fmap length -< bangedL 
    returnA -< filter ((== maxLen) . length) bangedL 

И это работает в GHCI, как и ожидалось:

ghci> composed ["alice", "bob", "david"] 
["alice!!!","david!!!"] 

Если вы заинтересованы , вы можете прочитать несколько руководств с хорошими изображениями, чтобы понять, что такое стрелка, и как эта мощная функция работает, чтобы вы могли погрузиться в нее глубже:

https://www.haskell.org/arrows/index.html

https://en.wikibooks.org/wiki/Haskell/Understanding_arrows

+1

Приятно отметить, хотя я бы сказал, что сила «proc» что он позволяет писать довольно общие композиции стрелок _as if_ они были функциями в синтаксисе лямбда. Если вы работаете над _is_ '(->)', то нет оснований не просто использовать привязку lambdas/'let ... in', чтобы выполнить одно и то же, если вы собираетесь предоставить эти промежуточные имена результатов в любом случае. – leftaroundabout

+0

@leftaroundabout Да, существуют стрелы помимо '(->)', и вы можете захотеть написать общие функции функций версии, хотя я не делал этого для этого случая, чтобы упростить пример. Ваша версия следует за мыслями из этого блога: http://degoes.net/articles/insufficiently-polymorphic. Но иногда имена переменных могут облегчить понимание кода (особенно для стрелок). – Shersh

5

Одним из возможных способов добиться чего-то подобного, что это стрелки. В принципе, в «сохранении промежуточных результатов» вы просто разделяете поток информации через цепочку композиций. Вот что делает комбинатор &&& (fanout).

import Control.Arrow 

composed = fmap (++ "!!!") 
     >>> ((. length) . (==) . maximum . fmap length &&& id) 
     >>> uncurry filter 

Это, безусловно, не очень хороший человеко-понятный код.

Государственная монада, похоже, тоже допускает что-то связанное, но проблема в том, что тип состояния фиксируется через монадическую цепочку блока do. Это не очень гибко, чтобы воспринимать разные типизированные значения во всей цепочке композиций. Хотя это, безусловно, возможно обойти это (среди них, действительно, RebindableSyntax), это тоже не очень хорошая идея ИМО.

+0

Hrmm, интересно! Я дал что-то подобное в приведенном выше редактировании, мне все же хотелось бы увидеть что-то немного более элегантное, но все это немного неудобно:/ –

1

У вас есть по существу фильтр, но тот, где функция фильтрации изменяется по мере перебора по списку. Я бы смоделировать это не как «раздвоенным» композиции, но как раз с использованием следующей функции: f :: String -> (Int, [String])

  1. Возвращаемое значение сохраняет текущий максимум и все строки этой длины.
  2. Если первый аргумент короче текущего максимума, оставьте его.
  3. Если первый аргумент совпадает с текущим максимумом, добавьте его в список.
  4. Если первый аргумент длиннее, сделайте его длину новым максимумом и замените текущий список вывода на новый список.

Как только складка завершена, вы просто извлекаете список из кортежа.

-- Not really a suitable name anymore, but... 
composed :: [String] -> [String] 
composed = snd . foldr f (0, []) 
    where f curr (maxLen, result) = let currLen = length curr 
            in case compare currLen maxLen of 
             LT -> (maxLen, result)  -- drop 
             EQ -> (maxLen, curr:result) -- keep 
             GT -> (length curr, [curr]) -- reset 
3

Тип (<*>) специализированы к функции экземпляра Applicative является:

(<*>) :: (r -> a -> b) -> (r -> a) -> (r -> b) 

Полученный r -> b функция передает свой аргумент к обоим r -> a -> b и r -> a функции, а затем использует значение a полученного по функции r -> a как второй аргумент r -> a -> b.

Что это связано с вашей функцией? filter - это функция из двух аргументов, предиката и списка. Теперь ключевым аспектом того, что вы пытаетесь сделать, является то, что предикат генерируется из списка. Это означает, что ядро ​​вашей функции может быть выражено в терминах (<*>):

-- Using the predicate-generating function from leftaroundabout's answer. 
maxLengthOnly :: Foldable t => [t a] -> [t a] 
maxLengthOnly = flip filter <*> ((. length) . (==) . maximum . fmap length) 

composed :: [String] -> [String] 
composed = maxLengthOnly . fmap (++ "!!!") 

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

Поскольку Applicative экземпляра функций эквивалентна по мощности к Monad один, maxLengthOnly также можно выразить как:

maxLengthOnly = (. length) . (==) . maximum . fmap length >>= filter 

(split вы добавили на свой вопрос, кстати, является (>>=) для функций .)

Другой способ написания его Applicative является:

maxLengthOnly = filter <$> ((. length) . (==) . maximum . fmap length) <*> id 

Не случайно это очень похоже на решение leftaroundabout: для функций, (,) <$> f <*> g = liftA2 (,) f g = f &&& g.

Наконец, следует также отметить, что, в то время как заманчиво заменить id в последней версии maxLengthOnly с fmap (++ "!!!"), что не будет работать, потому что fmap (++ "!!!") изменяет длину струн, и, следовательно, влияет на результат из сказуемое. С функцией, которая не аннулируют предикат, хотя, это будет работать очень хорошо:

nicerComposed = filter 
    <$> ((. length) . (==) . maximum . fmap length) <*> fmap reverse 
GHCi> nicerComposed ["alice","bob","david"] 
["ecila","divad"]