#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
также должно быть известно.
На практике это имеет два эффекта:
- Вы не можете объявлять какие-либо экземпляры, которые имеют одинаковые
container
, но разныеelement
имена. Нравится,Cons (Array Int) Int
иCons (Array Int) String
не будет работать. Компилятор будет жаловаться, что они нарушают функциональную зависимость. - Но в свою очередь компилятор теперь может делать выводы
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)