Являются ли расширяемые записи бесполезными в Elm 0.19? - PullRequest
1 голос
/ 06 апреля 2019

Расширяемые записи были одной из самых удивительных функций Elm, но начиная с v0.16 добавление и удаление полей больше не доступно . И это ставит меня в неловкое положение.

Рассмотрим пример. Я хочу дать имя случайной вещи t, и расширяемые записи предоставляют мне идеальный инструмент для этого:

type alias Named t = { t | name: String }

«Хорошо», говорит компилятор. Теперь мне нужен конструктор, то есть функция, которая оснащает вещь с указанным именем:

equip : String -> t -> Named t
equip name thing = { thing | name = name }  -- Oops! Type mismatch

Компиляция не удалась, потому что синтаксис { thing | name = ... } предполагает, что thing является записью с полем name, но система типов не может этого гарантировать. Фактически, с помощью Named t я пытался выразить нечто противоположное: t должен быть типом записи без собственного поля name, и функция добавляет это поле в запись. В любом случае, для реализации функции equip необходимо добавление поля.

Так что, кажется, невозможно написать equip полиморфно, но, вероятно, это не такая уж большая проблема. В конце концов, когда я собираюсь дать имя какой-то конкретной вещи, я могу сделать это руками. Гораздо хуже, обратная функция extract : Named t -> t (которая стирает имя именованной вещи) требует механизма удаления поля и, следовательно, также не реализуема:

extract : Named t -> t
extract thing = thing  -- Error: No implicit upcast

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

Наконец, после этого длинного вступления, позвольте мне сформулировать мои вопросы:

  1. Предоставляет ли современный Elm некоторую замену старому устаревшему синтаксису добавления / удаления полей?

  2. Если нет, есть ли какая-нибудь встроенная функция, такая как equip и extract выше? Для каждого настраиваемого расширяемого типа записи я хотел бы иметь полиморфный анализатор (функция, извлекающая его базовую часть) и полиморфный конструктор (функция, которая объединяет базовую часть с добавкой и создает запись).

  3. Отрицательные ответы для (1) и (2) заставили бы меня реализовать Named t более традиционным способом:

    type Named t = Named String t
    

    В этом случае я не могу уловить цель расширяемых записей. Есть ли положительный вариант использования, сценарий, в котором расширяемые записи играют критическую роль?

Ответы [ 2 ]

3 голосов
/ 07 апреля 2019

Тип { t | name : String } означает запись с полем name.Он не расширяет тип t, а скорее расширяет знания компилятора о t.

Так что на самом деле тип equip равен String -> { t | name : String } -> { t | name : String }.

ЧтоБолее того, как вы заметили, Elm больше не поддерживает добавление полей в записи, поэтому даже если система типов разрешает то, что вы хотите, вы все равно не сможете это сделать.Синтаксис { thing | name = name } поддерживает только обновление записей типа { t | name : String }.

Аналогично, не поддерживается удаление полей из записи.

Если вам действительно нужны типы, из которых вы можетеДобавить или удалить поля, которые вы можете использовать Dict.Другими вариантами являются либо написание преобразователей вручную, либо создание и использование генератора кода (это было рекомендованным решением для эталона JSON на некоторое время).

А что касается расширяемых записей, Elm на самом деле не поддерживает «расширяемая »часть, больше - единственная оставшаяся часть - это проекция { t | name : u } -> u, поэтому, возможно, ее следует называть просто записями в области видимости.Elm docs сам признает, что расширяемость в настоящее время не очень полезна.

2 голосов
/ 07 апреля 2019

Вы можете просто обернуть тип t с помощью name, но это не будет иметь большого значения по сравнению с подходом с пользовательским типом:

type alias Named t = { val: t, name: String }

equip : String -> t -> Named t
equip name thing = { val = thing, name = name }

extract : Named t -> t
extract thing = thing.val
...