Подобные типы записей в списке / массиве в purescript - PullRequest
0 голосов
/ 13 ноября 2018

Есть ли способ сделать что-то вроде

first = {x:0}
second = {x:1,y:1}
both = [first, second]

такой, что both выводится как {x::Int | r} или что-то в этом роде?

Я пробовал несколько вещей:

[{x:3}] :: Array(forall r. {x::Int|r})    -- nope

test = Nil :: List(forall r. {x::Int|r})
{x:1} : test                              -- nope

type X r = {x::Int | r}
test = Nil :: List(X)              -- nope
test = Nil :: List(X())
{x:1} : test
{x:1, y:1} : test                  -- nope

Все, о чем я могу думать, говорит мне о том, что объединение подобных записей в коллекцию не поддерживается. Вроде как, функция может быть полиморфной, а список - нет. Это правильная интерпретация? Это немного напоминает мне проблему «ограничения значений» в F #, хотя я думал, что это из-за ограничений CLR, тогда как у JS такой проблемы быть не должно. Но, может быть, это не связано.

Есть ли способ объявить список / массив для поддержки этого?

Ответы [ 2 ]

0 голосов
/ 14 ноября 2018

Ответ на вопрос @FyodorSoikin, основанный на «экзистенциальных типах» и на том, что мы можем найти в purescript-exists, мы можем предложить еще одно решение.Наконец, мы сможем построить Array записей, которые будут «изоморфны»:

exists tail. Array { x :: Int | tail }

Давайте начнем с конструктора типов, который можно использовать для экзистенциального количественного определения по типу строки (тип вида#Type).Мы не можем использовать Exists из purescript-exists здесь, потому что PureScript не имеет никакого полиморфизма, а оригинал Exists параметризован по Type.

newtype Exists f = Exists (forall a. f (a :: #Type))

Мы можем следовать и переопределять (<Ctrl-c><Ctrl-v>;-)) определения из Data.Exists и создайте набор инструментов для работы с такими Exists значениями:

module Main where

import Prelude

import Unsafe.Coerce (unsafeCoerce)
import Data.Newtype (class Newtype, unwrap)

newtype Exists f = Exists (forall a. f (a :: #Type)) 

mkExists :: forall f a. f a -> Exists f
mkExists r = Exists (unsafeCoerce r :: forall a. f a)

runExists :: forall b f. (forall a. f a -> b) -> Exists f -> b
runExists g (Exists f) = g f

Используя их, мы получим возможность построить Array из Records с «любым» хвостом, но мы должны обернуть любой такой тип записи в newtype до:

newtype R t = R { x :: Int | t }
derive instance newtypeRec :: Newtype (R t) _

Теперь мы можем построить Array, используя mkExists:

arr :: Array (Exists R)
arr = [ mkExists (R { x: 8, y : "test"}), mkExists (R { x: 9, z: 10}) ]

и значения процесса, используя runExists:

x :: Array [ Int ]
x = map (runExists (unwrap >>> _.x)) arr
0 голосов
/ 13 ноября 2018

То, что вы ищете, это " экзистенциальные типы ", и 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.

...