Подтвердить что ты не робот

Как сделать fmap переписать правила огня?

Простой вопрос: почему это не вызывает правило перезаписи?

{-# RULES "fmap/fmap" forall f g xs. fmap f (fmap g xs) = fmap (f.g) xs #-}

main = do
  txt <- fmap head (fmap words (readFile "foo.txt"))
  print txt

Теперь я хотел написать, что извлечение fun запускает правило, потому что это было в предыдущем тесте... не в этот раз.

{-# RULES "fmap/fmap" forall f g xs. fmap f (fmap g xs) = fmap (f.g) xs #-}

fun f g xs = fmap f (fmap g xs)

main = do
  txt <- fun (drop 1) words (readFile "foo.txt")
  print txt

До тех пор, пока я случайно не добавлю имя модуля:

module Main where

{-# RULES "fmap/fmap" forall f g xs. fmap f (fmap g xs) = fmap (f.g) xs #-}

fun f g xs = fmap f (fmap g xs)

main = do
  txt <- fun head words (readFile "foo.txt")
  print txt

Теперь он все еще не работает, если я просто напишу приложение функции в основной функции.

Подводя итог:

  • txt <- fmap head (fmap words (readFile "foo")) не работает
  • txt <- fun head words (readFile "foo") не работает
  • txt <- fun head words (readFile "foo") plus работает модуль
  • fun f g xs = fmap f . fmap g $ xs plus модуль не работает
  • fun f g xs = f <$> (g <$> xs) плюс модуль работает (но срабатывает позже)

Все это было сделано, вызвав ghc --make -O2 -ddump-rule-firings Main.hs. Пример вывода:

# ghc --make -O2 -ddump-rule-firings Main.hs
[1 of 1] Compiling Main             ( Main.hs, Main.o )
Rule fired: fmap/fmap
Rule fired: unpack
Rule fired: Class op >>=
Rule fired: Class op fmap
Rule fired: Class op fmap
Rule fired: Class op show
Rule fired: Class op showList
Rule fired: unpack-list
Linking Main ...
4b9b3361

Ответ 1

Учитывая, что сказал @Cactus, я считаю, что здесь происходит то, что правило Class op fmap заменяет ваш fmap его определение для IO:

instance  Functor IO where
    fmap f x = x >>= (pure . f)

Если это происходит повсюду, до того как ваше правило будет запущено, тогда не будет fmap слева (внутреннее представление GHC) вашего кода для запуска вашего собственного правила.

GHC пытается специализировать методы класса, когда они используются в определенных типах, поэтому, если используется монада fmap, полностью неизвестна, не будет никакого общего fmap после того, как он будет сделан специализацией.

Итак, оставшийся вопрос: почему ваше правило срабатывает, когда вы предоставляете заголовок модуля?

module Main where

Ответ заключается в том, как это немного отличается от заголовка модуля по умолчанию, который используется, если вы не предоставляете:

module Main (main) where

Обратите внимание, что это явно экспортирует только main из модуля. Ваша версия, без списка экспорта, вместо этого экспортирует все, что определено в модуле, как main, так и fun.

Когда экспортируется только main, GHC может вывести, что fun используется только внутри main, и встроить его полностью в него, не пытаясь сделать автономную версию. Затем он замечает, что fmap используется только для IO и специализируется на них. Или, возможно, это делается в обратном порядке, но конечный результат тот же.

Когда fun также экспортируется, GHC должен предположить, что пользователи вашего модуля могут захотеть называть его в любой монаде. Поэтому GHC затем компилирует автономную версию fun для общей монады, которая сохраняет fmap generic, и ваше правило может запускать эту версию.

Однако даже для явного кода module правило Class op fmap срабатывает дважды при компиляции, как если бы оно применялось к двум отдельным fmap s. Поэтому я подозреваю, что даже в этом случае fun встроен и специализирован в main, прежде чем ваше правило упростит его использование только одного fmap, поэтому встроенная версия, используемая внутри main, все равно не будет иметь ваше правило применяется к нему.