Какие шаги для выведения этого бессмысленного кода?

Я просматривал некоторый код и наткнулся на следующий драгоценный камень, на который я бы поставил 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)

Это не бессмысленный стиль, но довольно простой

Другие вопросы по тегам