Идиоматический способ реализации m (t a) -> (a -> m (t b)) -> m (t b)

Функция bind (>>=) имеет сигнатуру:

m a -> (a -> m b) -> m b

Однако мне нужна функция с подписью:

m (t a) -> (a -> m (t b)) -> m (t b)

В частности, у меня есть функция, которая, учитывая целое число, возвращает список целых чисел внутри IO:

f :: Int -> IO [Int]

но я хочу применить его к IO of list of Integers, и я не могу использовать обычную функцию привязки, потому что она заключена в два контейнера, то есть список, содержащийся в IO. Поиск в hoogle не помогает.

Я использую следующий подход для реализации этого:

Допустим, реализация функции такова:

f :: Int -> IO [Int]
f x = do
  return $ [x-10, x+10]

Я использую две вспомогательные функции, чтобы получить то, что я хочу:

f' :: [Int] -> IO [Int]
f' xs = do
  list <- traverse f xs
  return $ join list

f'' :: IO [Int] -> IO [Int]
f'' xs = do
  ys <- xs
  f' ys

Вышеизложенное работает, но я хотел бы знать, есть ли лучший/идиоматический способ реализовать это в haskell?


person mandark    schedule 04.08.2017    source источник
comment
Я не ищу решения, так как оно у меня уже есть. Я хочу идиоматический способ решить эту проблему.   -  person mandark    schedule 04.08.2017
comment
Я не знаю, читали ли вы ответы, но В более общем смысле, какая функция имеет тип m1 m2 a -> (a -> m1 m2 b) -> m1 m2 b? является частью этого дублирующего вопроса. Ответ: в общем случае это невозможно. Смотрите второй ответ.   -  person 4castle    schedule 04.08.2017
comment
Если вы удалите сигнатуры типов для f' и f'' и абстрагируете f в параметр, вы получите наиболее общий тип, выведенный средством проверки типов. Я бы сказал, что эти реализации идиоматичны, за исключением, возможно, использования нотации do вместо >>= и fmap, но это чисто стилистические проблемы. В нынешнем виде этот вопрос полностью основан на мнении - можете ли вы уточнить, что в вашем коде недостаточно хорошо, то есть чего вы хотели бы добиться в «лучшей» версии?   -  person user2407038    schedule 05.08.2017


Ответы (1)


Идиоматическим решением было бы использование Data.Functor.Compose:

data Compose f g a = Compose { getCompose :: f (g a) }

Поскольку функцию, которую вы ищете, тривиально реализовать, когда Compose f g является монадой:

bibind :: Monad (Compose f g) => f (g a) -> (a -> f (g b)) -> f (g b)
bibind m h = getCompose (Compose m >>= Compose . h)

Как хорошо объяснено в этом ответе, недостаточно, чтобы f и g были Monads, им также нужно коммутировать:

class Commute f g where
  commute :: g (f x) -> f (g x)

instance (Monad f, Monad g, Commute f g) => Monad (Compose f g) where
  return = Compose . return . return
  join = Compose .  fmap join .  join .  fmap commute . getCompose . fmap getCompose

(В общем, недостаточно, чтобы f было монадой, а g было Traversable)

person rampion    schedule 04.08.2017