Использование прикладного экземпляра составных функторов

#haskell #applicative

#haskell #аппликативный

Вопрос:

У меня есть две функции вида

 foo :: Int -> IO String

bar :: Int -> IO Integer
  

которые в основном живут в функторе, заданном композицией (->) Int и IO .
Теперь, имея функцию типа

 baz :: String -> Integer -> Float
  

Я хотел бы перенести его в Int -> IO _ контекст, используя прикладной синтаксис, например

 foobarbaz :: Int -> IO Float
foobarbaz = baz <$> foo <*> bar
  

Если я делаю это, компилятор кричит на меня с

 Couldn't match type `IO String' with `[Char]'
      Expected type: Int -> String
        Actual type: Int -> IO String
  

как если бы он пытался использовать прикладной экземпляр только для (->) Int .

Я думал, что аппликативные функторы составлены, чтобы я мог использовать аппликативный экземпляр для составного функтора. Я ошибаюсь? Или я должен просто предоставить больше информации компилятору?

Я попытался также включить TypeApplications явное указание функтора, который я хочу использовать, но я понял, что не могу писать (->) Int (IO _) . Есть ли на самом деле способ сделать это?

Ответ №1:

Я думал, что аппликативные функторы составлены, чтобы я мог использовать аппликативный экземпляр для составного функтора.

Это правильно, но представьте, что мы берем f ~ (->) Int , тогда типы являются (<$>) :: (a -> b) -> (Int -> a) -> (Int -> b) , и (<*>) :: (Int -> (a -> b)) -> (Int -> a) -> (Int -> b) , но это не соответствует типам, поскольку, baz требует a String и an Integer , тогда foo как возвращает an IO String и bar an IO Integer .

Вы можете использовать liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c , чтобы поднять baz функцию, чтобы использовать результат IO действий:

 import Control.Applicative(liftA2)

foobarbaz :: Int -> IO Float
foobarbaz = liftA2 baz <$> foo <*> bar  

liftA2 здесь, таким образом, преобразует a baz :: String -> Integer -> Float в a liftA2 baz :: IO String -> IO Integer -> IO Float . Таким образом, это функция, которая затем сопоставляется с типами вывода foo и bar .

Комментарии:

1. Спасибо! Это имеет смысл, в основном мне нужно поднимать один функтор одновременно. Я тоже мог бы сделать (liftA2 (liftA2 baz)) foo bar . Есть ли какая-либо конкретная причина, по которой компилятор не может сделать это всего за один подъем?

2. @marcosh: есть один лифт, который нужно поднять (-> Int) , но другой нужно поднять IO . Но вы правы, что можете использовать liftA2 (liftA2 baz) foo bar . Подъем всегда поднимается над «одним» прикладным уровнем (и из-за системы типов, которая всегда является самой внешней). Таким образом, он не «возвращается» к большему количеству уровней, поскольку это может привести к двусмысленности.

3. @marcosh Чтобы сделать это за один подъем, вы можете явно составить функторы с Data.Functor.Compose помощью , за счет некоторого переноса / разворачивания нового типа. hackage.haskell.org/package/base-4.14.0.0/docs /…