Поместите начальное и текущее состояние в кортеж и используйте fmap
, чтобы поднять функции, которые заботятся только о текущем состоянии:
ghci> :set -XTypeApplications
ghci> fmap @((,) SomeRecord) :: (a -> b) -> (SomeRecord, a) -> (SomeRecord, b)
Но что, если нам даны две функции в форме (SomeRecord,SomeRecord) -> SomeRecord
, и нам нужно их составить? Мы можем достаточно легко определить оператор, но существует ли он где-то уже?
Как это бывает, тип ((,) e)
имеет экземпляр Comonad
. Это очень простая comonad, которая связывает значения с некоторой средой - в нашем случае, с исходным значением, которое мы хотим сохранить.
Оператор композиции co-kleisli =>=
может использоваться для объединения двух функций (SomeRecord,SomeRecord) -> SomeRecord
вместе с =>>
для применения их к начальному парному значению.
ghci> import Control.Comonad
ghci> (1,7) =>> ((\(o,c) -> c * 2) =>= (\(o,c) -> o + c))
(1,15)
Или мы можем использовать =>>
полностью:
ghci> (1,7) =>> (\(o,c) -> c * 2) =>> (\(o,c) -> o + c)
(1,15)
Используя оператор flipped fmap
<&>
, мы даже можем написать конвейер, например
ghci> (1,2) =>> (\(x,y) -> y+2) <&> succ =>> (\(x,y) -> x + y)
(1,6)
Мы также можем использовать extract
, чтобы получить текущее значение, которое, возможно, лучше, чем snd
для показа намерения.