Purescript определяет cons как оператор класса типов

#purescript

#purescript

Вопрос:

Определен оператор Cons (:) определен для массива (Array.cons ) и списка (конструктор типов Cons). Поэтому, чтобы использовать его в коде, мы должны либо:

 import Data.List ((:))
 

или

 import Data.Array ((:))
 

Интересно, возможно ли определить (:) , чтобы его можно было импортировать в модуль и использовать для массива и списка в том же модуле.

Я пытался сделать это таким образом:

 class Cons container element where
  cons :: element -> container -> container

instance consArray :: Cons (Array a) a where
  cons = Array.cons

instance constList :: Cons (List a) a where
  cons = List.Cons

infixr 6 cons as :
 

И, похоже, это работает для базовых случаев:

 arr :: Array Int
arr = 1 : [2,3]

lst :: List Int
lst = 1 : (2 :3 : Nil)
 

Но в некоторых сложных случаях, например:

 data X a = X a

getX :: ∀ msg. Int -> X msg
getX = unsafeCoerce

getCons :: ∀ msg. 
  Array (X msg) ->
  Array (X msg)
getCons children = getX 1 : children

 

Но это приводит к ошибкам:

 No type class instance was found for

    MyModule.Cons (Array (X msg2))
                       (X t3)

  The instance head contains unknown type variables. Consider adding a type annotation.

while applying a function cons
  of type Cons t0 t1 => t1 -> t0 -> t0
  to argument getX 1
while inferring the type of cons (getX 1)
in value declaration getCons

where msg2 is a rigid type variable
        bound at (line 0, column 0 - line 0, column 0)
      t0 is an unknown type
      t1 is an unknown type
 

Итак, вопрос в том, возможно ли вообще достичь того, что я описал?

UPD:

Это:

 class Cons container where
  cons :: forall element. element -> container element -> container element
 

решает проблему унифицированного использования (:) при построении массива или списка.

Итак, после импорта это (:) мы можем сделать в том же модуле:

 arrFn :: ∀ a. a -> Array a -> Array a
arrFn el array = el : array

listFn :: ∀ a. a -> List a -> List a
listFn el list = el : el : list


-- and even this will work too:

data X a = X a

getX :: ∀ msg. Int -> X msg
getX = unsafeCoerce

getCons :: ∀ msg. 
  Array (X msg) ->
  Array (X msg)
getCons children = getX 1 : children
 

Проблема в том, что оператор, определенный таким образом, не может использоваться при сопоставлении с образцом (поскольку обработка шаблонов работает только с конструкторами типов), это приведет к ошибке:

 matchList :: List Int -> Int
matchList ls =
   case ls of
     Nil -> 0
     (x : xs) -> x 
 

И кажется, что невозможно добиться полностью универсального использования (:), чтобы оно работало для Array/List построения и List сопоставления с образцом.

Комментарии:

1. Вау. Вы приложили много усилий к этому вопросу 🙂

Ответ №1:

TL; DR: вам нужна функциональная зависимость.

Длинный ответ

Это происходит потому, что компилятор не знает, какой его экземпляр Cons следует искать.

В выражении getX 1 : children компилятор знает это children :: Array (X msg) , но каков тип getX 1 ? Функция getX может возвращать X любой тип, вообще любой. Так и должно быть X Int ? Или X String ? Или, может быть, X Boolean ? Невозможно определить! Таким образом, компилятор просто вызывает этот тип X t3 для некоторых, пока неизвестных t3 , и переходит оттуда, надеясь, что t3 это станет известно со временем.

Но это не так. Следующее, что должен решить компилятор, это применить (:) оператор. И для этого ему нужно найти экземпляр Cons (Array (X msg)) (X t3) , и он не знает как, потому что он не знает, что t3 это такое, и ни один из существующих экземпляров не соответствует этой форме.


Кстати, на этом этапе вы можете остановиться и проверить это. Измените свой экземпляр на этот:

 instance consArray :: Cons (Array a) b where
  cons _ xs = xs
 

После этого getCons внезапно компилируется. Почему? Потому что новый заголовок экземпляра Cons (Array a) b на самом деле соответствует требуемому Cons (Array (X msg)) (X t3) при замене a ~ X msg и b ~ X t3 . И на самом деле никогда не выясняется, что t3 есть на самом деле, поэтому его можно просто игнорировать.

Также, кстати, вам даже не нужно X для этого. Вы можете воспроизвести проблему только с помощью:

 getX :: forall a. Int -> a
getCons :: forall msg. Array msg -> Array msg
 

X это только запутывает дело.


Но «возможно ли вообще достичь того, что я описал«, спросите вы?

Ну, вы на самом деле не описали желаемый результат, так что трудно сказать наверняка. Но если бы мне пришлось гадать, мне кажется, что вы действительно хотели, чтобы компилятор выяснил, что, поскольку второй аргумент (:) является an Array (X msg) , то первый аргумент должен быть элементом этого массива, а именно, X msg , — и затем использовать эту информацию для определения предполагаемого типав getX этом экземпляре.

Если это действительно то, чего вы хотели достичь, то вам нужна функциональная зависимость:

 class Cons container element | container -> element where
                             ^^^^^^^^^^^^^^^^^^^^^^
                                    |
                               this bit here
 

Этот фрагмент синтаксиса сообщает компилятору, что если container каким-то образом известно, то element также должно быть известно.

На практике это имеет два эффекта:

  1. Вы не можете объявлять какие-либо экземпляры, которые имеют одинаковые container , но разные element имена. Нравится, Cons (Array Int) Int и Cons (Array Int) String не будет работать. Компилятор будет жаловаться, что они нарушают функциональную зависимость.
  2. Но в свою очередь компилятор теперь может делать выводы element , просто зная container .

Поэтому, если вы просто добавите этот бит, getCons все будет скомпилировано нормально: компилятор сначала поймет это container ~ Array (X msg) , поэтому он выберет экземпляр, который соответствует — Const (Array a) a , — и оттуда сделает вывод, что element ~ X msg и, следовательно getX :: Int -> X msg .


Старый ответ — написан до того, как вопрос был существенно изменен

Это не имеет ничего общего с определением оператора в классе типов. То же самое произошло бы, если бы вы просто импортировали оператор из Data.Array , за исключением того, что сообщение об ошибке было бы немного лучше.

Проблема в том, что вы пытаетесь сопоставить значение типа X a с массивом значений типа Tuple String (X a) . Типы разные. Tuple String (X a) это не то же X a самое, что .

Чтобы заставить его работать, вам нужно либо исправить сигнатуру типа, чтобы типы были одинаковыми:

 xFn :: ∀ a. Tuple String (X a) -> Array (Tuple String (X a)) -> Array (Tuple String (X a))
xFn el list = el : list
 

Или вам нужно создать кортеж из el , прежде чем пытаться использовать его:

 xFn :: ∀ a. X a -> Array (Tuple String (X a)) -> Array (Tuple String (X a))
xFn el list = Tuple "foo" el : list
 

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


Или, в качестве альтернативы, вы можете предоставить экземпляр Cons (Array (Tuple String a)) a и преобразовать элемент в кортеж внутри этого экземпляра. Отсутствие такого экземпляра — это то, на что жалуется компилятор в сообщении об ошибке. Это может выглядеть примерно так:

 instance consTuple :: Cons (Array (Tuple String a)) a where
    cons a xs = cons (Tuple "foo" a) xs
 

За исключением того, что такой экземпляр не будет работать, потому что он будет перекрываться с consArray . Итак, вы могли бы исправить это, поместив их в цепочку:

 instance consArray :: Cons (Array a) a where
  cons = Array.cons
else instance consTuple :: Cons (Array (Tuple String a)) a where
  cons a xs = cons (Tuple "foo" a) xs
 

Хотя я не могу себе представить, какой возможный вариант использования вы могли бы использовать для этого.

Комментарии:

1. Извините, на самом деле это была моя ошибка, я обновил код операции, чтобы показать фактическую ошибку, которая работает с (:) из массива

2. Спасибо за проработку, это было полезно -) Я обновил OP (UPD: section), чтобы объяснить решение, которое я нашел, хотя оно не работает для сопоставления списка с шаблоном.

3. Я не понимаю вашего комментария, сондам нравится изобретать, чтобы не задавать вопрос по SO)