Одно поле слишком много
Иногда не удается найти mapN
с достаточно высоким N
я ›Ааааа, только не снова!
ват ›Ненавижу звук разочарования по утрам. Что случилось?
я ›Что случилось? Я тебе скажу, что случилось! Вот что не так:
type alias Person = { firstName: String , lastName: String , age: Int , email: String , phone: String , hairColor: String , eyeColor: String , favoriteFood: String , favoriteDrink: String }
wat ›Это похоже на очень хороший псевдоним типа (если немного шире). Что насчет этого?
я ›Раньше у него было на два поля меньше (favoriteFood
и favoriteDrink
- поздние добавления), поэтому, когда я хотел декодировать его из JSON, я мог просто использовать Decode.map7
и покончить с этим:
type alias Person = { firstName: String , lastName: String , age: Int , email: String , phone: String , hairColor: String , eyeColor: String } personDecoder : Decoder Person personDecoder = Decode.map7 Person (field "firstName" string) (field "lastName" string) (field "age" int) (field "email" string) (field "phone" string) (field "hairColor" string) (field "eyeColor" string)
ват ›Так оно и было. Теперь тебе нужен map9
, я думаю ...
меня ›… которого, как оказалось, не существует. Или, по крайней мере, не в Json.Decode
.
Ват ›Что ж, некоторые люди¹ скажут вам, что это проблема дизайна, и что вы должны использовать вместо этого вложенную структуру, группируя поля, которые идут вместе, и помещая их в меньшие блоки, которые вы можете поместить в большую коробку, и т. д.
я ›Ну, конечно, но… разве нет… функционального способа сделать это без необходимости рефакторинга всего моего кода, чтобы освободить место для новых полей?…
ват ›Вы каким-то образом пытаетесь заставить меня раскрыть более запретные, эзотерические знания глубокой, темной информатики?
я да?
ват ›Ну, это… очевидно, работает. Вот взгляните на эту интригующую новую функцию:
-- (from Json.Decode.Extra) andMap : Decoder a -> Decoder (a -> b) -> Decoder b
я ›Погодите ... разве я не знаю это откуда-то? ... Имя знакомое ...
wat ›Вы можете найти это похоже на Json.Decode.map
, если переключите параметры (и немного прищурились):
-- (from Json.Decode.Extra) andMap : Decoder a -> Decoder (a -> b) -> Decoder b -- (from Json.Decode) map : (a -> b) -> Decoder a -> Decoder b
я ›Ага ... но это как если бы мы поменяли местами функцию преобразования с декодером ... и почему-то обернули ее в Decoder
. Зачем нам заключать функцию в Decoder
?
ват ›Скажем сейчас, это не функция. Допустим, это «пустой b
», для заполнения которого требуется только a
.
я ›Хм… ладно. Итак, это конструктор разновидностей?
ват ›Конечно. Теперь предположим, что andMap
похож на роботизированную руку, которая может вытащить a
из Decoder a
и поместить это a
внутрь нашего «пустого b
», чтобы получился полный b
.
я ›Понял.
wat ›Давайте посмотрим, как это работает, на простейшем примере - декодере, который берет строковое поле и передает его очень простому человеку:
type alias Person = { firstName: String } personDecoder : Decoder Person personDecoder = andMap (field "firstName" string) -- : Decoder String (succeed Person) -- : Decoder (String -> Person)
я ›Звучит ... бесполезно сложно. Но да, вы берете строковый декодер, который извлекает строку из поля firstName
, andMap
каким-то образом разворачивает ее, а затем передает ее конструктору Person
в своем всегда последующем декодере. И я предполагаю, что когда Decoder String
выходит из строя, мы получаем Decoder Person
просто неисправный декодер. Думаю, я понял.
ват ›Теперь давайте запишем это более конвейерно:
personDecoder = succeed Person |> andMap (field "firstName" string)
я ›Хорошо, это то же самое, только мы передаем конструктору в его всегда преуспевающем декодере andMap
, используя (|>)
. Ну и что?
ват ›А теперь давайте добавим в Person
еще одно поле, которое тоже нужно расшифровать:
type alias Person = { firstName: String , lastName: String } personDecoder : Decoder Person personDecoder = succeed Person |> andMap (field "firstName" string) |> andMap (field "lastName" string)
ват ›Не требуется mapN
😉
я ›Ой. Это похоже на то, как если бы ваши роботизированные руки были связаны вместе, чтобы сформировать некую сборочную линию, в которой элементы один за другим помещаются в наш конструктор… Великолепно!
ват ›Да! Давайте вернемся к нашему определению andMap
, не так ли?
andMap : Decoder a -> Decoder (a -> b) -> Decoder b
ват ›Итак, у нас есть этот Person : String -> String -> Person
конструктор, который хочет, чтобы две строки строили его Person
значение, верно? И у нас есть два декодера, которые могут декодировать строки. Но эти декодеры могут выйти из строя. Итак, сначала мы превращаем конструктор в успешный Decoder
и используем andMap
, чтобы объединить их с операцией декодирования:
succeed Person -- this is a Decoder (String -> String -> Person) |> andMap (field "firstName" string) -- the result is a Decoder (String -> Person) -- (the first String is provided by the first decoder) |> andMap (field "lastName" string) -- the result is a Decoder Person -- (now it can decode a whole Person :)
я ›Дай мне попробовать и…
type alias Person = { firstName: String , lastName: String , age: Int , email: String , phone: String , hairColor: String , eyeColor: String , favoriteFood: String , favoriteDrink: String } personDecoder : Decoder Person personDecoder = succeed Person|> andMap
(field "firstName" string)|> andMap
(field "lastName" string)|> andMap
(field "age" int)|> andMap
(field "email" string)|> andMap
(field "phone" string)|> andMap
(field "hairColor" string)|> andMap
(field "eyeColor" string)|> andMap
(field "favoriteFood" string)|> andMap
(field "favoriteDrink" string)
я ›😢 это так красиво…
ват ›И знаете что?… Так же, как map
- это сверхважная функция Функтора, а andThen
Монада, есть имя для вещей, над которыми вы можете andMap
…
я ›Позже. Позвольте мне насладиться этим чувством.
wat ›… и это имя - Applicative Functor. Мы скоро попробуем обобщить . Пока.
[1] не слушайте этих людей; ваши большие плоские предметы в порядке, если вы думаете, что они в порядке; вы должны группироваться, когда это имеет для вас смысл, а не для того, чтобы угодить кому-то другому. Кроме того, те же люди, как правило, все равно находят ваши объекты слишком глубоко вложенными.