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

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

Кто-нибудь будет достаточно любезен, чтобы объяснить шаги, необходимые для упрощения от:

(i) coordinates b = (x b, y b)

до:

(ii) coordinates = liftM2 (,) x y?

В частности, меня немного смущает использование liftM2, поскольку я даже не знал, что на заднем плане скрывается монада.

Я знаю, что (i) также может быть представлено как: coordinates s = (,) (x s) (y s), но я не уверен, где и как действовать дальше.


P.S. Вот почему я подозреваю, что это от pointfree (вывод от GHCI, а :pl имеет псевдоним pointfree):

λ: :pl coordinates s = (x s, y s)
coordinates = liftM2 (,) x y

person iceman    schedule 19.11.2014    source источник


Ответы (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)

После того, как вы усвоите идею такой многофункциональной композиции, такой бесточечный код станет немного более читабельным — нет необходимости использовать инструмент без точек! В этом случае я думаю, что бесточечная версия все же лучше, но сама бесточечная не так уж и ужасна.

person Tikhon Jelvis    schedule 19.11.2014

Здесь работает монада 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")
person bheklilr    schedule 19.11.2014

Вы могли бы легко переписать

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)

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

person viorior    schedule 20.11.2014