Какие шаги для выведения этого бессмысленного кода?
Я просматривал некоторый код и наткнулся на следующий драгоценный камень, на который я бы поставил pointfree
выход:
(Я думал, что следующее будет более подходящим, чем обычно foo
/bar
для этого конкретного вопроса:P)
import Control.Monad (liftM2)
data Battleship = Battleship { x :: Int
, y :: Int
} deriving Show
placeBattleship :: Int -> Int -> Battleship
placeBattleship x' y' = Battleship { x = x', y = y' }
coordinates :: Battleship -> (Int, Int)
coordinates = liftM2 (,) x y
Будет ли кто-то достаточно любезен, чтобы объяснить шаги, необходимые для упрощения:
(я) coordinates b = (x b, y b)
чтобы:
(II) coordinates = liftM2 (,) x y
?
В частности, я немного запутался в отношении использования liftM2
поскольку я даже не знал, что на заднем плане скрывалась монада.
Я знаю, что (я) также может быть представлен как: coordinates s = (,) (x s) (y s)
но я не уверен, где / как действовать.
PS Вот почему я подозреваю, что это из pointfree
(выход из GHCI
а также :pl
связан с pointfree
):
λ: :pl coordinates s = (x s, y s)
coordinates = liftM2 (,) x y
3 ответа
Это использует преимущества Monad
экземпляр для (->) r
также называется "читатель монада". Это монада функций из определенного типа в a
, (Посмотрите здесь для мотивации, почему он существует в первую очередь.)
Чтобы увидеть, как это работает для различных функций, замените m
с (r ->
в m a
, Например, если мы просто делаем liftM
, мы получаем:
liftM :: (a -> b) -> (m a -> m b)
liftM :: (a -> b) -> ((r -> a) -> (r -> b))
:: (a -> b) -> (r -> a) -> (r -> b) -- simplify parentheses
... которая просто функциональная композиция. Ухоженная.
Мы можем сделать то же самое для liftM2
:
liftM2 :: (a -> b -> c) -> m a -> m b -> m c
liftM2 :: (a -> b -> c) -> (r -> a) -> (r -> b) -> (r -> c)
Итак, мы видим способ составления двух функций с одним аргументом с помощью функции с двумя аргументами. Это способ обобщения нормальной композиции функций для нескольких аргументов. Идея состоит в том, что мы создаем функцию, которая принимает один r
передавая это через обе функции с одним аргументом, получая два аргумента для передачи в функцию с двумя аргументами. Так что если у нас есть f :: (r -> a)
, g :: (r -> b)
а также h :: (a -> b -> c)
, мы производим:
\ r -> h (f r) (h r)
Теперь, как это относится к вашему коду? (,)
является функцией с двумя аргументами, и x
а также y
являются функциями одного аргумента типа Battleship -> Int
(потому что так работают полевые методы доступа). Имея это в виду:
liftM2 (,) x y = \ r -> (,) (x r) (y r)
= \ r -> (x r, y r)
После того, как вы усвоили идею составления нескольких функций, подобную этой, бессмысленный код, подобный этому, станет немного более читабельным - нет необходимости использовать инструмент pointfree! В этом случае, я думаю, что безочковая версия все же лучше, но сама по себе не страшна.
Монада liftM2
работает здесь, это функция монада (->) a
, Это эквивалентно Reader
монада, как вы могли видеть раньше.
Напомним определение liftM2
:
liftM2 :: Monad m => (a -> b -> r) -> m a -> m b -> m r
liftM2 f ma mb = do
a <- ma
b <- mb
return $ f a b
Так что теперь, если мы подставим в (,)
за f
, x
за ma
, а также y
за mb
, мы получаем
liftM2 (,) x y = do
a <- x
b <- y
return $ (,) a b
поскольку x, y :: Battleship -> Int
что эквивалентно ((->) Battleship) Int
, затем m ~ (->) Battleship
, Функция монада определяется как
instance Monad ((->) a) where
return x = const x
m >>= f = \a -> f (m a) a
По сути, функция, которую выполняет монада, позволяет извлекать выходные данные из нескольких функций при условии, что все они имеют одинаковый вход. Более понятным примером может быть что-то вроде
test = do
a <- (^2)
b <- (^3)
c <- (^4)
d <- show
return (a, b, c, d)
> test 2
(4, 8, 16, "2")
Вы могли бы легко переписать
data Battleship = Battleship { x :: Int
, y :: Int
} deriving Show
placeBattleship :: Int -> Int -> Battleship
placeBattleship x y = Battleship x y
coordinates :: Battleship -> (Int, Int)
coordinates (Battleship x y) = (x, y)
Это не бессмысленный стиль, но довольно простой