#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 /…