Состав функции 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!!!"]

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

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

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

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

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]):

  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
Другие вопросы по тегам