Функциоанльный стейт это - функция которая возвращает пару, где первый элемент активный, а второй представляет хранилище. По дефолту второй элемент такого же типа как и первый, это значит если оба параметра пары будут Int, то в хранилище мы сможем хранить только один элемент. Позже мы расмотрим вариант где второй параметр будет списком, а пока давайте разберемся с концепцией функциоанльного стейта.
И так - как же нам записать в функцию что-то? как функция может хранить значение?
type State<A> = () => A
state: State<number> = () => 1
state() // 1
Вот простой пример как функция хранит внутри себя константу, можно думать о функции как о контейнере который хранить значение, а для того чтоб его достать из контейнера нужно ее вызвать
Чтобы мы могли вставить любое значение, нам понадобится сделать функцию с одним параметром, которая создает наш контейнер State.
Такая функция будет называтся pure
type PureS<A> = (a: A) => State<A>
pure = a => () => a
const s0 = pure(1)
const s1 = pure(s0() + 1)
s1() // 2
const s2 = pure(s1() * 2)
s2() // 4
// теперь запишем без констант
pure(
pure(
pure(
1
)() + 1 // 2
)() * 2 // 4
)() // 4
// теперь развернем pure, подставим вместо него, его содержимое (это не обязательно понимать, это просто для любопытсва)
(_ =>
(_ =>
(_ => 1)() + 1
)() * 2 // 4
)() // 4
Теперь чтоб не работать в ручную с нашей оберткой,
чтоб не вызывать ее каждый раз для того чтоб достать значение,
чтоб не вызывать каждый раз pure чтоб обратно завернуть значение в обертку,
сделаем функцию хелпер
который будет принимать
f: A => B
- функцию которая будет получать значние из State, и возвращать другое значениеma: State<A>
- нашу обертку State из которого нужно получить значение,mb: State<B>
- в результате будет распоковыватьma
,
передавать это значение в нашу функциюf
,
получение значение от функции заворачивать в новый Statemb
type fmap<A, B> = (f: (a:A) => B, ma: State<A>) => State<B>
fmap = (f, state) => pure(f(state()))
s = fmap(
x => x * 2,
fmap(
x => x + 1,
pure(1)
)
)
s() // 4
Мы только что реализовали интерфейс функтора для контейнера State
, интерфейс функтора включает в себя фунцию fmap
интерфейс функтора в хаскеле
class Functor m where
fmap :: (a -> b) -> m a -> m b
про то как читать типы написанные на хаскеле см тут
именнования переменных
именновать переменные я буду как в хаскеле, посмотрите на тип fmap
, там есть ma
, в хаскеле m
называют любую обертку которая может хранить в себе какое-то значение,
ma
- значение a
в обертке m
.
переменные, я буду именовать в алфавитном порядке - a, b, c, ...,
и дженерики для них соответсвующи
Такая запись на жс не очень красивая, есть вложенности, надо соблюдать отсупы чтоб это было более менне читаемо, и надо читать с нутри наружу, Это все потому-что я показываю имплементацию стейта, функтора ровно такими как они описанные в хаскеле. но там это не вызывает таких проблем с читаемостью, так как там есть операторы
пример на хаскеле
fmap = (<$>)
оператор <$> правоассоциативный, это значит что выполнение будет справо на лево.
(\x -> x * 2) <$> (\x -> x + 1) <$> pure 1
эквиваленто
fmap
(\x -> x * 2)
(
fmap
(\x -> x + 1)
pure 1
)
более читаемый способ записи для js
мы можем воспользоваться объектами жсса, чтоб чейнить методы через точку, путем возвращения самого себя каждый раз,
получится алтернативная замена операторам хаскеля
class State {
constructor(a) { this.state = () => a }
static pure(a) { return new State(a) }
runState() { return this.state() }
fmap(f) {
return State.pure(
f(this.state())
)
}
}
console.log(
State.pure(1).fmap(x => x+1).fmap(x => x*2).runState()
)
...