То, что вы ищете, это " экзистенциальные типы ", и PureScript просто не поддерживает их на уровне синтаксиса, как это делает Haskell.Но вы можете свернуть свое собственное: -)
Один из способов - это «абстракция данных» - т.е. кодировать данные в терминах операций, которые вы хотите выполнить над ним.Например, допустим, вы захотите получить значение x
из них в какой-то момент.В этом случае создайте массив из них:
type RecordRep = Unit -> Int
toRecordRep :: forall r. { x :: Int | r } -> RecordRep
toRecordRep {x} _ = x
-- Construct the array using `toRecordRep`
test :: Array RecordRep
test = [ toRecordRep {x:1}, toRecordRep {x:1, y:1} ]
-- Later use the operation
allTheXs :: Array Int
allTheXs = test <#> \r -> r unit
Если у вас есть несколько таких операций, вы всегда можете записать их:
type RecordRep =
{ getX :: Unit -> Int
, show :: Unit -> String
, toJavaScript :: Unit -> Foreign.Object
}
toRecordRep r =
{ getX: const r.x
, show: const $ show r.x
, toJavaScript: const $ unsafeCoerce r
}
(обратите внимание на Unit
аргументы в каждой функции - они там для лени, предполагая, что каждая операция может быть дорогой)
Но если вам действительно нужен тип машины , вы можете сделать то, что я называю "беднякомэкзистенциальный тип ".Если вы присмотритесь, экзистенциальные типы - это не что иное, как «отложенные» проверки типов - отложенные до точки, где вам нужно будет увидеть тип.И каков механизм, чтобы отложить что-то на языке ML?Это верно - функция!: -)
newtype RecordRep = RecordRep (forall a. (forall r. {x::Int|r} -> a) -> a)
toRecordRep :: forall r. {x::Int|r} -> RecordRep
toRecordRep r f = f r
test :: Array RecordRep
test = [toRecordRep {x:1}, toRecordRep {x:1, y:1}]
allTheXs = test <#> \(RecordRep r) -> r _.x
Способ, которым это работает, заключается в том, что RecordRep
оборачивает функцию, которая принимает другую функцию, которая полиморфна в r
- то есть, если вы смотрите на RecordRep
, вы должны быть готовы дать ему функцию, которая может работать с любым r
.toRecordRep
упаковывает запись таким образом, что ее точный тип не виден снаружи, но он будет использоваться для создания экземпляра обобщенной функции, которую вы в конечном итоге предоставите.В моем примере такой функцией является _.x
.
Обратите внимание, однако, что в этом заключается проблема: строка r
буквально не известна, когда вы начинаете работать с элементом массива, поэтому вы можетеничего не делать с этим.Мол, вообще.Все, что вы можете сделать, это получить поле x
, потому что его существование жестко закодировано в сигнатурах, но кроме x
- вы просто не знаете.И это специально: если вы хотите поместить что-нибудь в массив, вы должны быть готовы извлечь из него что-нибудь .
Теперь, если вы хотитеВ конце концов, чтобы что-то сделать со значениями, вам придется объяснить это, ограничив r
, например:
newtype RecordRep = RecordRep (forall a. (forall r. Show {x::Int|r} => {x::Int|r} -> a) -> a)
toRecordRep :: forall r. Show {x::Int|r} => {x::Int|r} -> RecordRep
toRecordRep r = RecordRep \f -> f r
test :: Array RecordRep
test = [toRecordRep {x:1}, toRecordRep {x:1, y:1}]
showAll = test <#> \(RecordRep r) -> r show
Передача функции show
, как это работает, потому что мы ограничили строкуr
таким образом, что Show {x::Int|r}
должен существовать, и, следовательно, применение show
к {x::Int|r}
должно работать.При необходимости повторите для своих собственных классов типов.
И вот интересная часть : поскольку классы типов реализованы в виде словарей функций, две описанные выше опции фактически эквивалентны - в обоих случаях выв конечном итоге мы передаем словарь функций, только в первом случае это явно, но во втором случае компилятор сделает это за вас.
Кстати, так работает поддержка языка Haskell.