Вы путаете нечеткие типы с экзистенциальными.Необычные типы позволяют вам помещать полиморфные значения в структуру данных, а не произвольные конкретные.Другими словами [forall a. Num a => a]
означает, что у вас есть список, где каждый элемент работает как любой числовой тип, поэтому вы не можете поместить, например, Int
и Double
в список типа [forall a. Num a => a]
, но вы можете поместить что-то вроде0 :: Num a => a
в нем.Здесь вам нужны не только предикативные типы.
Вам нужны экзистенциальные типы, т. Е. [exists a. Num a => a]
(не настоящий синтаксис Haskell), который говорит, что каждый элемент имеет некоторый неизвестный числовой тип.Однако, чтобы написать это на Haskell, нам нужно ввести тип данных оболочки:
data SomeNumber = forall a. Num a => SomeNumber a
Обратите внимание на изменение с exists
на forall
.Это потому, что мы описываем конструктор .Мы можем поместить любой числовой тип в , но тогда система типов «забудет», какой это был тип.Как только мы уберем его обратно (путем сопоставления с образцом), все, что мы знаем, это то, что это некоторый числовой тип.Под капотом происходит то, что тип SomeNumber
содержит скрытое поле, в котором хранится словарь классов типов (он же vtable / implicit), поэтому нам нужен тип оболочки.
Теперь мы можем использоватьвведите [SomeNumber]
для списка произвольных чисел, но нам нужно обернуть каждое число в пути, например, [SomeNumber (3.14 :: Double), SomeNumber (42 :: Int)]
.Правильный словарь для каждого типа ищется и сохраняется в скрытом поле автоматически в точке, где мы переносим каждое число.
Комбинация экзистенциальных типов и классов типов в некоторой степени аналогична подтипам, поскольку основное отличие между классами типов и интерфейсами состоит в том, что с классами типов vtable перемещается отдельно от объектов, а экзистенциальные типы упаковывают объекты иvtables снова вместе.
Однако, в отличие от традиционных подтипов, вы не обязаны соединять их один в один, поэтому мы можем написать такие вещи, которые упаковывают одну vtable с двумя значениями одного типа.
data TwoNumbers = forall a. Num a => TwoNumbers a a
f :: TwoNumbers -> TwoNumbers
f (TwoNumbers x y) = TwoNumbers (x+y) (x*y)
list1 = map f [TwoNumbers (42 :: Int) 7, TwoNumbers (3.14 :: Double) 9]
-- ==> [TwoNumbers (49 :: Int) 294, TwoNumbers (12.14 :: Double) 28.26]
или даже более причудливые вещи.Как только мы создадим образец соответствия на оболочке, мы снова окажемся в стране классов типов.Хотя мы не знаем, какие типы x
и y
, мы знаем, что они одинаковы, и у нас есть правильный словарь для выполнения числовых операций над ними.
Все вышеперечисленное работает аналогично с несколькими типами классов.Компилятор просто сгенерирует скрытые поля в типе оболочки для каждого vtable и выведет их все в область видимости, когда мы сопоставим шаблон.
data SomeBoundedNumber = forall a. (Bounded a, Num a) => SBN a
g :: SomeBoundedNumber -> SomeBoundedNumber
g (SBN n) = SBN (maxBound - n)
list2 = map g [SBN (42 :: Int32), SBN (42 :: Int64)]
-- ==> [SBN (2147483605 :: Int32), SBN (9223372036854775765 :: Int64)]
Поскольку я очень начинающий, когда речь заходит о Scala, яЯ не уверен, что смогу помочь с заключительной частью вашего вопроса, но я надеюсь, что это, по крайней мере, прояснило некоторую путаницу и дало вам некоторые идеи о том, как поступить.