Изучение Haskell - Как упростить выражения?

Есть ли способ избавить боль от упрощения выражения?

Например, учитывая это выражение:

(+) <$> a <*> b $ 1

Мне бы хотелось увидеть инструмент, который бы объяснил, что это значит. Это очень трудоемко для новичков (поиск правильного определения функции экземпляра в источниках, проверка приоритета оператора) для упрощения выражений со всеми задействованными шагами:

fmap (+) a <*> b $ 1

См. определение в Data.Functor

(.) (+) a <*> b $ 1  

См. fmap в Control.Monad.Instances для instance Functor ((->) r)

и т.д.

EDIT: Чтобы уточнить, я ищу способ переписать выражение с использованием фактических определений функций, чтобы новичок мог понять результат этого выражения. Как сказать, что (<$>) = fmap здесь? Я не знаю, как найти определение конкретного экземпляра (источник) с помощью hoogle и других инструментов.

EDIT: Изменено неправильное исходное выражение для соответствия следующим сокращениям.

Ответ 1

Я считаю, что простой способ - использовать типизированные отверстия, доступные в GHCi 7.8:

> (*10) <$> _a $ 1
Found hole ‘_a’ with type: s0 -> b
Where: ‘s0’ is an ambiguous type variable
       ‘b’ is a rigid type variable bound by
           the inferred type of it :: b at <interactive>:4:1
Relevant bindings include it :: b (bound at <interactive>:4:1)
In the second argument of ‘(<$>)’, namely ‘_a’
In the expression: (* 10) <$> _a
In the expression: (* 10) <$> _a $ 1

Итак, это говорит мне, что a :: s0 -> b. Далее следует выяснить порядок операторов:

> :i (<$>)
(<$>) :: Functor f => (a -> b) -> f a -> f b
infixl 4 <$>
> :i ($)
($) :: (a -> b) -> a -> b
infixr 0 $

Итак, это говорит о том, что $ является очень право-ассоциативным, и, учитывая его тип, мы видим, что его первый аргумент должен быть функцией, поэтому a должна быть функцией (двойное подтверждение). Это означает, что (*10) <$> a $ 1 совпадает с ((*10) <$> a) $ 1, поэтому сначала мы сосредоточимся на (*10) <$> a.

> :t ((*10) <$>)
((*10) <$>) :: (Num a, Functor f) => f a -> f a
> :t (<$> _a)
Found hole ‘_a’ with type: f a
Where: ‘a’ is a rigid type variable bound by
           the inferred type of it :: (a -> b) -> f b at Top level
       ‘f’ is a rigid type variable bound by
           the inferred type of it :: (a -> b) -> f b at Top level
In the second argument of ‘(<$>)’, namely ‘_a’
In the expression: (<$> _a)

Итак, нам нужно a быть функтором. Какие существуют экземпляры?

> :i Functor
class Functor (f :: * -> *) where
  fmap :: (a -> b) -> f a -> f b
  (<$) :: a -> f b -> f a
        -- Defined in ‘GHC.Base’
instance Functor Maybe -- Defined in ‘Data.Maybe’
instance Functor (Either a) -- Defined in ‘Data.Either’
instance Functor ZipList -- Defined in ‘Control.Applicative’
instance Monad m => Functor (WrappedMonad m)
  -- Defined in ‘Control.Applicative’
instance Control.Arrow.Arrow a => Functor (WrappedArrow a b)
  -- Defined in ‘Control.Applicative’
instance Functor (Const m) -- Defined in ‘Control.Applicative’
instance Functor [] -- Defined in ‘GHC.Base’
instance Functor IO -- Defined in ‘GHC.Base’
instance Functor ((->) r) -- Defined in ‘GHC.Base’
instance Functor ((,) a) -- Defined in ‘GHC.Base’

Итак, (->) r оказывается одним, что является удивительным, потому что мы знаем, что a должна быть функцией. Из ограничения Num мы можем определить, что r должен быть таким же, как Num a => a. Это означает, что (*10) <$> a :: Num a => a -> a. Из этого мы затем применим к нему 1, и мы получим (*10) <$> a $ 1 :: Num a, где a - некоторая неизвестная функция.

Все это можно обнаружить с помощью GHCi, используя :t и :i, а также типизированные отверстия. Конечно, существует множество шагов, но это никогда не сбой, когда вы пытаетесь сломать сложное выражение, просто посмотрите на типы разных подвыражений.

Ответ 2

GHCi была чудесно и правильно предложена, и я тоже предлагаю.

Я также хочу предложить Hoogle, поскольку при включенном мгновенном поиске (в верхней боковой панели справа есть кнопка для нее), вы можете искать функции очень быстро, он может предоставить гораздо больше информации, чем GHCi, и самое главное, что вам не нужно упоминать модули для поиска в них 1. Это контрастирует с GHCi, где вам нужно сначала импортировать:

ghci> :t pure
<interactive>:1:1: Not in scope: ‘pure’
ghci> :m +Control.Applicative
ghci> :t pure
pure :: Applicative f => a -> f a

Ссылка Hoogle выше всего одна (с сайта Haskell.org). Hoogle - это программа, которую вы также можете установить на свой компьютер (cabal install hoogle) и выполнить запросы из командной строки (hoogle your-query).
Sidenote: вам нужно запустить hoogle data, чтобы сначала собрать информацию. Для этого требуется wget/curl, так что если вы находитесь в Windows, вам, вероятно, нужно будет this в вашем пути сначала (или завиток для Windows, конечно). В Linux он почти всегда встроен (если у вас его нет в Linux, просто apt-get он). Я никогда не использую Hoogle из командной строки, это просто не так доступно, но все равно может быть очень полезно, потому что некоторые текстовые редакторы и их плагины могут воспользоваться им.

В качестве альтернативы вы можете использовать FPComplete Hoogle, что иногда более удовлетворительно (потому что, по моему опыту, он знает больше сторонних библиотек. используйте его в тех "сессиях Hoogling" ).

Также существует Hayoo!.

1 В Hoogle вы, вероятно, > 95% времени не должны этого делать, но +Module для импорта модуля, если по какой-либо причине его не ищут (в этом случае иногда для сторонних библиотек).
Вы также можете отфильтровать модули на -Module.
Например: destroyTheWorld +World.Destroyer -World.Destroyer.Mercy найти destroyTheWorld и убедиться, что вы не смотрите на милостивый способ сделать это (это очень удобно с модулями с одинаковыми именами функций для разных версий, такими как те, что указаны в Data.ByteString и Data.ByteString.Lazy, Data.Vector и Data.Vector.Mutable и т.д.).

О, и еще одно удивительное преимущество Hoogle заключается в том, что он не только показывает вам подпись функции, но также может доставлять вас на страницы модуля Haddock, так что вы также получаете документацию + на этих страницах, когда это возможно, вы можете нажать на "Источник" справа от каждой функции, чтобы увидеть, как она выполняется для получения еще большей информации.

Это выходит за рамки вопроса, но Hoogle также используется для запроса сигнатур функций, которые просто... разумно полезны. Если мне нужна функция, которая принимает номер индекса и список, и дает мне элемент в этом индексе, и мне интересно, если он уже встроен, я могу найти его в течение нескольких секунд.
Я знаю, что функция принимает число и список и дает мне элемент списка, поэтому подпись функции должна выглядеть примерно так: Int -> [a] -> a (или вообще: Num a => a -> [b] -> b), и оба экземпляра показывают, что там действительно является функцией для этого ((!!) и genericIndex).

В тех случаях, когда GHCi имеет преимущество, вы можете играть с выражениями, исследовать их и т.д. Много раз при работе с абстрактными функциями, что много значит.
Возможность :l (oad) очень полезна как хорошо.

Если вы просто ищете сигнатуры функций, вы можете комбинировать как Hoogle, так и GHCi.
В GHCi вы можете ввести :! cmd, а GHCi выполнит cmd в командной строке и распечатает результаты. Это означает, что вы также можете использовать Hoogle внутри GHCi, например. :! hoogle void.

Ответ 3

Запустите ghci, :cd в базовый каталог источника, который вы читаете, :load интересующий вас модуль и используйте команду :i, чтобы получить информацию:

ghci> :i <$>
(<$>) :: Functor f => (a -> b) -> f a -> f b
    -- Defined in `Data.Functor'
infixl 4 <$>
ghci> :i $
($) :: (a -> b) -> a -> b   -- Defined in `GHC.Base'
infixr 0 $
ghci> :i .
(.) :: (b -> c) -> (a -> b) -> a -> c   -- Defined in `GHC.Base'
infixr 9 .

Указывает тип, где он определен, ассоциативность (infixl или infixr) и приоритет (число, более высокое). Поэтому (*10) <$> a $ 1 читается как ((*10) <$> a) $ 1.

Когда вы :load модуль, все имена, которые находятся в области видимости внутри этого модуля, будут находиться в области внутри ghci. Одно место, где это может раздражать, - это если у вас есть ошибка в коде, тогда вы не можете :i что-либо внутри него. В этих случаях вы можете прокомментировать строки, использовать undefined и, возможно, также использовать напечатанные отверстия, как предлагает behlkir (не играл с этими слишком много).

Пока вы на нем, попробуйте выполнить команду :? в ghci.