Состав функции Do Notation
Существует ли синтаксический сахар "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!!!"]
Я не уверен, что что-то подобное возможно, так как результат предыдущей функции по сути должен проходить "через" привязку 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)))
4 ответа
Как leftaroundabout упоминалось, вы можете использовать Arrows
написать свою функцию. Но в компиляторе ghc Haskell есть функция, которая proc
примечание для стрелок. Это очень похоже на известные do
примечание, но, к сожалению, не многие знают об этом.
С proc
-примечание вы можете написать желаемую функцию следующим более читаемым и элегантным способом:
{-# 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!!!"]
Если вам интересно, вы можете прочитать несколько уроков с красивыми картинками, чтобы понять, что такое стрелка и как работает эта мощная функция, чтобы вы могли углубиться в нее:
Одним из возможных способов достижения чего-то подобного являются стрелки. По сути, в "хранении промежуточных результатов" вы просто разделяете поток информации по цепочке композиций. Вот что &&&
(фанат) комбинатор делает.
import Control.Arrow
composed = fmap (++ "!!!")
>>> ((. length) . (==) . maximum . fmap length &&& id)
>>> uncurry filter
Это определенно не хороший, понятный человеку код.
Казалось бы, монада состояний допускает нечто связанное, но проблема в том, что тип состояния фиксируется через do
монадическая цепь блока. Это не достаточно гибко, чтобы подбирать значения разных типов по всей цепочке композиции. Хотя это, безусловно, можно обойти (среди них, действительно, RebindableSyntax
), это тоже не очень хорошая идея ИМО.
Тип (<*>)
специализируется на экземпляре функции 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
определение было бы неплохо, если бы функция, генерирующая предикаты, не была такой неуклюжей.
Так как Applicative
экземпляр функции по мощности эквивалентен Monad
один, maxLengthOnly
также можно сформулировать как:
maxLengthOnly = (. length) . (==) . maximum . fmap length >>= filter
(The 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"]
По сути, у вас есть фильтр, но тот, в котором фильтрующая функция изменяется по мере итерации по списку. Я бы смоделировал это не как "раздвоенную" композицию, а как складку, используя следующую функцию f :: String -> (Int, [String])
:
- Возвращаемое значение поддерживает текущий максимум и все строки этой длины.
- Если первый аргумент короче текущего максимума, отбросьте его.
- Если первый аргумент совпадает с текущим максимумом, добавьте его в список.
- Если первый аргумент длиннее, задайте его длину новым максимумом и замените текущий список вывода новым списком.
Когда сворачивание завершено, вы просто извлекаете список из кортежа.
-- 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