Как реализовать типы сумм в динамически типизированных языках без сопоставления с образцом

У меня есть только теоретическое представление о типах сумм Haskell. И все же я чувствую, что они действительно важны в Haskell и коренным образом меняют способ моделирования ваших данных. Поскольку я считаю, что они могут быть полезны и в языках с динамической типизацией, я попытался реализовать разумное приближение в Javascript (у меня лишь поверхностное знание Haskell).

Вот более или менее полезный пример типа суммы Name, который может обрабатывать различные форматы имен. Я знаю, что Haskell различает конструкторы типов и конструкторы данных и, вероятно, имеет веские причины для такого различия. Однако я предполагаю, что невозможно сопоставить эту концепцию с Javascript. И ни одно из них не соответствует образцу.

Во всяком случае, мой фактический вопрос: воплощает ли следующая реализация природу типов сумм и то, как они применяются в Haskell?

Обратите внимание: я не уверен, что такие межъязыковые вопросы приветствуются на SO. Пожалуйста, дайте мне знать, если я должен избегать их.

// auxiliary functions

const A = f => x => f(x);
const show = api => api.show;

// the type constructor

const Name = (...xs) => A(({length: len}) => {
  switch (len) {
    case 1: {
      let [{length: len}] = xs; // no pattern matching but destructuring

      if (len > 1) { // no Char type in JS
        return k => k({show: () => xs[0]}); // returns the API
      }

      break;
    }

    case 2: {
      let [{length: len}, {length: len2}] = xs;

      if (len > 1 && len2 > 1) {
        return k => k({show: () => `${xs[0]} ${xs[1]}`});
      }

      if (len === 1 && len2 > 1) {
        return k => k({show: () => `${xs[0]}. ${xs[1]}`});
      }

      break;
    }

    case 3: {
      let [{length: len}, {length: len2}, {length: len3}] = xs;

      if (len > 1 && len2 > 1 && len3 > 1) {
        return k => k({show: () => `${xs[0]} ${xs[1]} ${xs[2]}`});
      }

      if (len > 1 && len2 === 1 && len3 > 1) {
        return k => k({show: () => `${xs[0]} ${xs[1]}. ${xs[2]}`});
      }

      if (len === 1 && len2 === 1 && len3 > 1) {
        return k => k({show: () => `${xs[0]}. ${xs[1]}. ${xs[2]}`});
      }
    }

    default: throw new TypeError();
  }
}) (xs);

// run

console.log(Name("Kerouac") (show) ());
console.log(Name("Hans", "Hölzel") (show) ());
console.log(Name("H", "Curry") (show) ());
console.log(Name("Jean", "Luc", "Godard") (show) ());
console.log(Name("William", "S", "Burroughs") (show) ());
console.log(Name("E", "W", "Dijkstra") (show) ());

[ИЗМЕНИТЬ]

Извините, я должен был предоставить код на Haskell:

type FirstName = String
type LastName = String
type MiddleName = String

data Name = FullName FirstName LastName
  | NameWithMiddle FirstName MiddleName LastName
  | NameWithMiddleInitial FirstName Char LastName
  | TwoInitialsWithLast Char Char LastName
  | OneInitialWithLast Char LastName
  | LastNameOnly LastName

Хотя я не уверен, что это действительно так.

Я предполагаю, что проблема с моим подходом заключается в том, что я пытаюсь реализовать конструктор типа Name, в то время как я должен реализовать конструкторы значений, верно?


person Community    schedule 05.03.2017    source источник
comment
Не могли бы вы показать определение типа Haskell, который вы пытаетесь имитировать?   -  person 4castle    schedule 06.03.2017
comment
Не уверен, что ожидаемый результат это?   -  person guest271314    schedule 06.03.2017
comment
Я понятия не имею, как приведенный выше код будет связан с типом суммы. Тип суммы имеет два конструктора (или N>=2 конструктора в его N-арном варианте) и один деструктор/исключатель с 2 (или N) аргументами. Возможно, вы захотите посмотреть кодировку Черча типов суммы.   -  person chi    schedule 06.03.2017
comment
Межъязыковые вопросы в целом допустимы на SO, но мое единственное предостережение состоит в том, что вы не тратите слишком много времени, пытаясь сжать язык, пока он не станет похож на другой. Синтаксис и функции будут другими, поэтому вместо этого сосредоточьтесь на том, как бы вы это сделали на этом конкретном языке. JavaScript уникален тем, что позволяет использовать множество различных парадигм, но не забывайте о читабельности. Старайтесь быть простым.   -  person 4castle    schedule 06.03.2017
comment
Эта реализация «типа суммы», по-видимому, делает две вещи, которые отличают ее от реальных типов суммы; во-первых, это кодирование СПС, но не суммы, а произведения в каждом варианте суммы ("сумма" - это переключатель); и то, что обычно называют «интеллектуальным конструктором», встроено в само определение типа. У вас также есть «тип», который по сути Either Char String, но его гораздо проще написать как (Bool, String), особенно в JS. Хотя я вижу, что «технически» это тип суммы, в Haskell он не используется таким образом (особенно при встраивании интеллектуальных конструкторов в тип).   -  person user2407038    schedule 06.03.2017
comment
Да, смешивать типы с конструкторами — очень плохая идея. Вы должны реализовать такое же различие в своем коде JS.   -  person Bergi    schedule 06.03.2017
comment
Кстати, ваш оператор switch выглядит так, как будто вы удачно успешно сопоставили концепцию сопоставления с образцом с JS.   -  person Bergi    schedule 06.03.2017
comment
@ user2407038 Кодировка CPS наверняка знакома практикам объектно-ориентированного программирования. Они называют это «паттерном посетителя».   -  person pyon    schedule 06.03.2017
comment
@Bergi Кодировка JavaScript для сопоставления с образцом выполняется с потерями. При подлинном сопоставлении с образцом аргументы конструктора доступны только в той ветви, которая обрабатывает этот конкретный конструктор.   -  person pyon    schedule 06.03.2017
comment
@ user2407038, bergi, это было полезно, спасибо. Я думаю, вы подтолкнули меня в правильном направлении.   -  person    schedule 06.03.2017
comment
@4castle Я добавил тип Haskell, как я бы его определил.   -  person    schedule 06.03.2017
comment
@ftor Возможно, что-то вроде daggy? Он использует своего рода кодирование Черча с некоторыми синтаксическими тонкостями.   -  person phipsgabler    schedule 06.03.2017


Ответы (1)


Я думаю, что ваша кодировка слишком сложна, потому что она включает не только сам тип данных, но и операцию show.

Суть типов суммы заключается в том, что они позволяют создавать значения, представляющие один или несколько случаев, а затем писать код обработки с использованием сопоставления с образцом. Немного более простая версия вашего примера с полным или коротким именем будет:

data Name = 
    FullName String String
  | NickName String

Затем вы можете написать функцию для обработки имени, обрабатывая два случая по отдельности:

case name of
  FullName first last -> "Full name: " ++ first ++ " " ++ last
  NickName nick -> "Nick name: " ++ nick

Основная идея заключается в том, что, когда у вас есть имя, вы можете определить, какой из двух случаев оно представляет, и написать код для обработки двух случаев по отдельности. В JavaScript вы можете сделать это, включив какой-то тег в ваше значение:

function FullName(first, last) { 
  return { Tag: "FullName", Values: [first, last] };
}
function NickName(nick) { 
  return { "Tag": "NickName", Values: [nick] };
}

Теперь вы можете написать код, аналогичный сопоставлению с образцом, используя switch на Tag:

switch(name.Tag) { 
  case "FullName": 
    let [first, last] = name.Values;
    return "Full name: " + first + " " + last;
  case "NickName":
    let [nick] = name.Values
    "Nick name: " + nick;
}

Однако без языковой поддержки вы теряете многие приятные функции:

  • Никаких проверок при извлечении значений нет. Если вы измените тип и добавите больше полей, сопоставление с образцом начнет давать сбой.
  • Нет никаких проверок, охватываете ли вы все случаи — когда вы добавляете другое имя, операторы switch не будут охватывать это.
  • Нет проверки имен тегов. Легко сделать опечатку.

Таким образом, вы, безусловно, можете использовать этот тип кодирования в JavaScript, но он не дает вам столько, сколько в языках, которые напрямую поддерживают типы суммы. Если для проблемы, с которой вы столкнулись, существует более идиоматичное JavaScript-решение, оно, вероятно, будет работать лучше.

person Tomas Petricek    schedule 06.03.2017
comment
Функциональное программирование включает в себя неизменяемость, поэтому потеря этой первой функции не должна быть проблемой. - person 4castle; 06.03.2017
comment
@4castle Нет, изменить тип там относится к редактированию исходного кода для изменения количества полей в одном из случаев (например, понимание того, что нам нужно дополнительное поле в случае Fullname для титула человека), ничего общего с изменчивостью значения во время выполнения. - person Ben; 11.03.2017