Size: a a a

Elm Lang сообщество разработчиков

2017 October 28

AP

Aleksei (astynax) Pirogov in Elm Lang сообщество разработчиков
Проблема в том, что type alias, это слабый тип (вообще не тип, просто новое имя для существующего типа). Пусть в случае с рекордами это не так важно, потому как разные по составу рекорды считаются разными подтипами, но всё же
источник

ST

Slava Turchaninov in Elm Lang сообщество разработчиков
Arthur Welf
Да, я смотрел, в том числе, и этот вариант. Более того - именно его я взял за основу, усовершенствовав его.

Усовершенствования касаются прежде всего использования extensible records. Поясню, что я имею в виду.

Model у меня состоит из трёх главных частей, каждая из которых представлена в виде extensible record'а:

1. Информация, необходимая для идентификации пользователя, который лазает по странице - его userId, token и список его ролей, в виде LoggedIn { userId : String, ... }. Эта информация таскается по всем частям приложения, т.к. она необходима везде. Когда это информации нет (например, юзер не залогинен или не зарегистрирован), вместо неё подставляется AnonimousUser.

2. Информация о user input на той или иной странице. Например, когда юзер заполняет поля в своем профиле, все введенные им данные хранятся тут, а потом, после валидации и проверки данных на полноту, отправляются на сервер, чтобы создать нового юзера или отредактировать существующий профиль.

Поскольку у нас на разных страницах разный user input, то он у меня реализован в виде union type - для каждого типа страницы своя ветка.

Сам по себе user input у меня тоже реализован в виде extensible record, т.к. мы же его потом отправляем на сервер, а там сервер добавляет к нему служебные поля тип ID, createdAt, updatedAt и т.д. - и всё, что юзер ввёл + служебные поля мы затем получаем в виде данных с сервера. Соответственно, данные с сервера у меня расширяют дополнительными полями данные user input'а.

3. Ну и, собственно, данные с сервера. Они нужны для каждой страницы свои, поэтому представлены также в виде union type.

В итоге модель у меня выглядит так:
type alias Model =
   AuthData (PageData (UserInputData {}))

type alias AuthData a =
   { a | user : User }

type alias PageData a =
   { a | page : Page }

type alias UserInputData a =
   { a | userInput : UserInput }


А транслируется Model из этого типа, состоящего из набора extensible records, в это:
type alias Model =
   { user : User
   , page : Page
   , userInput : UserInput
   }
у меня была модель всего проложения и отдельные модули для каждой страницы, примерно так:

type alias Model =
 { user: UserContext
 , page: Page }

type Page
 = Index Page.Index.Model


В Page.Index

init : UserContext -> (Model, Cmd Msg)
init userContext =
 (Model userContext ...., Cmd.none)

type alias Model =
 { user: UserContext
 , ... }


type Msg
 = ...
источник

AW

Arthur Welf in Elm Lang сообщество разработчиков
Aleksei (astynax) Pirogov
Удобно, но даже в доке этот способ считается "прикльным, но на практике - редким" :)
Я думаю, он редкий прежде всего потому, что о нем знает немного людей. Между тем у него есть множество полезных применений. Вот несколько навскидку:

1. Использование в качестве "интерфейсов", к чему привыкли, скажем, ООП-программисты, чтобы реализовать принцип DRY.

Если у меня на странице редактирования профиля юзера все поля из рекорда user input имеются также и в рекорде, где хранятся данные, полученные с сервера (+ там хранится ещё несколько полей, типа userId, которые мы не даем редактировать юзеру), то зачем мне определять два рекорда, у которых пересекаются 90% полей, если я могу определить один из этих рекордов в качестве extensible record, а во втором - просто добавить к нему недостающие поля (причем получив при этом flat record, в котором удобнее обновлять данные, а не вложенные рекорды, для обновления данных в которых мне надо постоянно использовать let ... in)?

2. Использование extensible records позволяет мне определять "полиморфные" функции для работы с различными рекордами с разными полями. Если в моем же примере с рекордом, содержащим user input data, и с рекордом, содержащим данные с сервера, который построен на основе предыдущего рекорда, мне нужно изменить одни и те же поля, то я могу написать одну функцию для этого и она будет работать с обоими рекордами.

3. Улучшение читаемости сигнатуры типов функций. Если в моем примере рекорд PageData является расширением рекорда UserInputData a, и мне нужно написать функцию, изменяющую только те поля, которые содержатся в рекорде UserInputData a, то в сигнатуре этой функции я напишу в качестве типа принимаемого значения UserInputData a (хотя в качестве аргумента буду передавать рекорд PageData) - и, взглянув на сигнатуру этой функции, я сразу понимаю, что она не затрагивает поля, которыми рекорд PageData расширяет мой extensible record UserInputData a.

В общем, еще раз повторюсь, что, на мой взгляд, eztensible records редко используются не потому, что они бесполезны, а потому, что многие о них не знают.

P.S.: Более подробно то, что описывается в пунктах 2 и 3, описано здесь: https://medium.com/@ckoster22/advanced-types-in-elm-extensible-records-67e9d804030d
источник

AW

Arthur Welf in Elm Lang сообщество разработчиков
Slava Turchaninov
у меня была модель всего проложения и отдельные модули для каждой страницы, примерно так:

type alias Model =
 { user: UserContext
 , page: Page }

type Page
 = Index Page.Index.Model


В Page.Index

init : UserContext -> (Model, Cmd Msg)
init userContext =
 (Model userContext ...., Cmd.none)

type alias Model =
 { user: UserContext
 , ... }


type Msg
 = ...
А что из себя представляет тип UserContext?
источник

ST

Slava Turchaninov in Elm Lang сообщество разработчиков
Arthur Welf
А что из себя представляет тип UserContext?
Что-то похожее на

type alias UserContext =
 { sid: String
 , info: UserType
 , location: Location
 , ...
 }

type UserType
 = Anonymous
 | Info UserInfo

type alias UserInfo =
 { id: Int
 , ...
 }
источник

AP

Aleksei (astynax) Pirogov in Elm Lang сообщество разработчиков
> Улучшение читаемости сигнатуры типов функций

Не улучшает наслаивание алиасов читаемость. В вашем примере.
источник

к

кана in Elm Lang сообщество разработчиков
Не понял, а нужно ли наслаивание алисов вообще? Нельзя определить конечный рекорд для модели, а в функциях использовать только подрекорд в типах?
источник

AP

Aleksei (astynax) Pirogov in Elm Lang сообщество разработчиков
> Если в моем примере рекорд PageData является расширением рекорда UserInputData a, и мне нужно написать функцию, изменяющую только те поля, которые содержатся в рекорде UserInputData a, то в сигнатуре этой функции я напишу в качестве типа принимаемого значения UserInputData a (хотя в качестве аргумента буду передавать рекорд PageData) - и, взглянув на сигнатуру этой функции, я сразу понимаю, что она не затрагивает поля, которыми рекорд PageData расширяет мой extensible record UserInputData a.

по сигнатуре UserInputData непонятно, что она расширяет
источник

AP

Aleksei (astynax) Pirogov in Elm Lang сообщество разработчиков
кана
Не понял, а нужно ли наслаивание алисов вообще? Нельзя определить конечный рекорд для модели, а в функциях использовать только подрекорд в типах?
Нужно делать WithUserData, WithInput как модификаторы для конкретного типа
источник

AP

Aleksei (astynax) Pirogov in Elm Lang сообщество разработчиков
Они всё равно коммутативные
источник

AP

Aleksei (astynax) Pirogov in Elm Lang сообщество разработчиков
WithA (WithB {}) == WithB (WithA {})
источник

AP

Aleksei (astynax) Pirogov in Elm Lang сообщество разработчиков
type alias Foo = { foo: () }

type alias Bar = { Foo | bar : () }

а так нельзя делать
источник

AP

Aleksei (astynax) Pirogov in Elm Lang сообщество разработчиков
Поэтому и думать об иерархии не нужно, ибо её нет. Есть set расширений с нефиксированным порядком
источник

AW

Arthur Welf in Elm Lang сообщество разработчиков
Aleksei (astynax) Pirogov
> Если в моем примере рекорд PageData является расширением рекорда UserInputData a, и мне нужно написать функцию, изменяющую только те поля, которые содержатся в рекорде UserInputData a, то в сигнатуре этой функции я напишу в качестве типа принимаемого значения UserInputData a (хотя в качестве аргумента буду передавать рекорд PageData) - и, взглянув на сигнатуру этой функции, я сразу понимаю, что она не затрагивает поля, которыми рекорд PageData расширяет мой extensible record UserInputData a.

по сигнатуре UserInputData непонятно, что она расширяет
Ну, может, в данном конкретном примере нейминг неудачный, но я имею в виду вот что:

Допустим, у нас есть блог, и страница создания/редактирования статьи. Юзер хочет создать статью, и для этого ему нужно заполнить следующие поля: название статьи, текст статьи, теги. Пока юзер все это заполняет, мы сохраняем введенные данные в рекорде, который я (возможно, неудачно) назову UserInputData:

type alias UserInputData a =
   { name : String
   , content : String
   , tags : List String
   }


После этого юзер закончил печатать и нажал кнопку "Запостить статью". Мы берём данные, которые он ввел, проверяем их на валидность, смотрим, заполнены ли все необходимые поля, и, если все ОК, то отправляем все эти данные на сервер.

Но после того, как юзер отправил статью на сервер, мы хотим показать ему то, как статья будет выглядеть в блоге. Поэтому мы запрашиваем у сервера вновь созданную статью, и ее название, содержание, список тегов, имя автора и дату создания. Эти все данные мы сохраняем в рекорде PageData и рендерим по этим данным HTML:

type alias PageData =
   { name : String
   , content : String
   , tags : List String
   , author : Author
   , createdAt : Date
   }


Нетрудно заметить, что поля name, content и tags присутствуют и в рекорде UserInputData, и в рекорде  PageData. И, чтобы не повторяться, мы просто определяем рекорд PageData как расширение рекорда UserInputData a, добавив в него лишь специфичные для PageData поля:

type alias PageData =
   UserInputData
       { author : Author
       , createdAt : Date
       }
источник

AP

Aleksei (astynax) Pirogov in Elm Lang сообщество разработчиков
Я подчеркну, лучше такие расширения называть WithSmth, тогда будет понятно, что это "миксины" к некоторому типу
источник

AP

Aleksei (astynax) Pirogov in Elm Lang сообщество разработчиков
А сам то принцип понятен и польза от него тоже заметна
источник
2017 October 29

AW

Arthur Welf in Elm Lang сообщество разработчиков
И, вдогонку к холивару Elm vs Haskell. Чего мне ещё реально не хватает в Elm из того, что есть в Haskell - это undefined, которым можно закрыть на время пока не написанную функцию и проверить, что всё остальное компилируется. Вот реально не хватает ))
источник

Вл

В ладу in Elm Lang сообщество разработчиков
Arthur Welf
И, вдогонку к холивару Elm vs Haskell. Чего мне ещё реально не хватает в Elm из того, что есть в Haskell - это undefined, которым можно закрыть на время пока не написанную функцию и проверить, что всё остальное компилируется. Вот реально не хватает ))
вот уж хз)
можно перебиться
источник

к

кана in Elm Lang сообщество разработчиков
Не
источник

к

кана in Elm Lang сообщество разработчиков
Не
источник