Nano Hash - криптовалюты, майнинг, программирование

Не удалось сопоставить ожидаемый тип с фактическим типом. Переменные типа неоднозначны

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

{-# LANGUAGE TypeFamilies, FlexibleInstances #-}

class Vector v where
  type Scalar v :: *
  vplus :: v -> v -> v
  vmult :: v -> Scalar v -> v
  vdot  :: v -> v -> Scalar v

instance Num a => Vector (a, a) where
  type Scalar (a,a) = a
  (a, b) `vplus` (c, d) = (a + c, b + d)
  (a, b) `vmult` m = (a * m, b * m)
  (a, b) `vdot`  (c, d) = a * c + b * d

Проблема в том, что мне нужны явные объявления типов для GHC, чтобы не запутаться. Это, конечно, небольшое неудобство, за исключением того, что vdot, похоже, вообще не хочет работать.

res :: Int
res = (2, 3) `vdot` (5, 5)
-- error: Couldn't match expected type 'Int' with actual type 'Scalar (t0, t1)'
--        The type variables 't0', 't1' are ambiguous

Эта ошибка исчезнет, ​​если я сделаю это:

res :: Int
res = ((2, 3) :: (Int, Int)) `vdot` (5, 5)

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

Я бы предположил, что GHC способен разрешить type Scalar (a, a) = a самостоятельно, но ошибка сохраняется, даже если я полностью удаляю объявление экземпляра. Он даже жалуется, когда Vector (Int, Int) является единственным доступным экземпляром.

Так что же здесь происходит? И есть ли способ заставить это работать красиво?


Ответы:


1

In

res :: Int
res = (2, 3) `vdot` (5, 5)

ничто не заставляет 2 и 3 быть Int или даже быть одного типа друг с другом. Таким образом, экземпляр Vector (a, a) может не применяться. Насколько известно GHC, вы можете захотеть написать еще один экземпляр Vector (Float, Double) с type Scalar (Float, Double) = Int и совершенно другой реализацией vdot, а res по-прежнему будет проверять тип. Следовательно, типы 2 и 3 неоднозначны, как сказал вам GHC.

Похоже, вы действительно хотите сказать: пара (a, b) может только быть экземпляром Vector, когда b имеет тот же тип, что и a (и затем использовать написанный вами экземпляр). Вы можете выразить это в GHC следующим образом:

instance (a ~ b, Num a) => Vector (a, b) where
  type Scalar (a,b) = a
  (a, b) `vplus` (c, d) = (a + c, b + d)
  (a, b) `vmult` m = (a * m, b * m)
  (a, b) `vdot`  (c, d) = a * c + b * d

a ~ b — это ограничение равенства, утверждающее, что два типа a и b одинаковы.

Теперь ваш пример res может работать правильно:

*Main> (2, 3) `vdot` (5, 5) :: Int
25

Вот рассуждение, которое означает, что типы больше не являются неоднозначными.

  • vdot имеет тип Vector v => v -> v -> Scalar v. Итак, чтобы res набрала проверку, нам нужно найти v, чтобы (2, 3) :: v, (5, 5) :: v и Scalar v ~ Int.

  • Но (2, 3) имеет тип формы (a, b) и есть экземпляр, голова которого имеет форму Vector (a, b). Итак, мы должны использовать этот экземпляр. (В вашей исходной программе мы не можем сделать этот шаг, потому что нет достаточно общего экземпляра.)

  • Определение связанного типа этого экземпляра говорит нам, что Scalar (a, b) ~ a. Но мы знаем, что Scalar (a, b) должно было быть Int, поэтому у нас должно быть a ~ Int.

  • Ограничения этого экземпляра говорят нам a ~ b и что должен быть экземпляр Num a. Значит, у нас тоже должно быть b ~ Int (и действительно Num Int имеет место).

  • Итак, мы пришли к выводу, что 2 :: Int и 3 :: Int, а так как еще и (5, 5) :: v, то у нас есть еще и 5 :: Int и 5 :: Int.

  • Теперь мы определили, какой класс типов использовать для каждого перегруженного имени в нашем выражении (2, 3, 5, 5 и vdot), так что двусмысленности нет, и мы наконец можем вычислить выражение.

23.12.2015
  • Я бы подумал, что если он говорит Scalar (a, a) и Vector (a, a), то единственным доступным типом является a. Так что, если a является Int, каждый другой экземпляр a должен также быть Int 23.12.2015
  • Я думаю, вы просто предполагаете, что будет выбран ваш экземпляр Vector (a, a). Дело в том, что нет ничего, что заставляло бы его выбирать, и на самом деле вы могли бы добавить второй, неперекрывающийся экземпляр, который также будет проверять тип проверки, если он используется в res. GHC никогда не предполагает, что экземпляр будет выбран только потому, что он доступен, ему нужно найти экземпляр, используя логику, изложенную во второй половине моего ответа. 23.12.2015
  • но если это единственный доступный экземпляр, как тогда он не обязательно выбирает его? Разве это не похоже на квадратное отверстие и квадрат, который не может войти, потому что он может быть кругом? 23.12.2015
  • Поскольку классы типов открыты (любой может определить дополнительные экземпляры в любом месте), компилятор никогда не предполагает, что у него есть полная информация о том, какие экземпляры существуют. Таким образом, Vector (a, a) может быть не единственным доступным экземпляром, который подойдет. На практике это означает, что компилятор не позволит вам писать программы, которые могут стать неоднозначными, если вы (или модули, которые вы импортируете) определяете дополнительные неперекрывающиеся экземпляры. 23.12.2015
  • Ваша аналогия плохо построена. Это не кусок, а дыра, форма которой неизвестна или гибкая. Мы не хотим брать на себя обязательство отправить квадратный кусок сегодня, потому что завтра мы можем получить другой кусок, который так же хорошо поместится по почте. 23.12.2015

  • 2

    Упростим дело:

    class Vector v where
      type Scalar v :: *
      vdot  :: v -> v -> Scalar v
      ...    
    instance Num a => Vector (a, a) where
      type Scalar (a,a) = a
      ...
    res :: Int
    res = (2, 3) `vdot` (5, 5)
    

    Теперь у нас есть

    vdot :: v     -> v  -> Scalar v
    vdot    (2,3) (5,5)
    

    поэтому двойное приложение должно иметь этот тип

    (2,3) :: v
    (5,5) :: v
    res = vdot (2,3) (5,5) :: Scalar v
    

    Расширение типа пар:

    (2,3) :: (a1, a2) ~ v     for some a1, a2 in class Num
    (5,5) :: (b1, b2) ~ v     for some b1, b2 in class Num
    res = vdot (2,3) (5,5) :: Scalar v
    

    По транзитивности (a1, a2) ~ (b1, b2), следовательно, a1 ~ b1 и a2 ~ b2.

    (2,3) :: (a1, a2)         for some a1, a2 in class Num
    (5,5) :: (a1, a2)
    res = vdot (2,3) (5,5) :: Scalar (a1, a2)
    

    Мы также знаем из аннотации, что

    res :: Int
    

    следовательно

    Scalar (a1, a2) ~ Int
    

    Но из этого невозможно узнать, что такое a1, a2. В конце концов, для этого можно использовать пользовательские типы:

    data A1 = ...
    data A2 = ...
    instance Num A1 where ...
    instance Num A2 where ...
    instance Vector (A1, A2) where
       type Scalar (A1, A2) = Int  -- !!!!
    

    Обратите внимание на последнее Int. Это вызывает оба

    type Scalar (Int, Int) = Int
    type Scalar (A1 , A2 ) = Int
    

    что делает невозможным выбор типов a1, a2. Обычно это обозначается предложением «семейства типов не обязательно должны быть инъективными».

    Также обратите внимание, что отсутствие instance Vector (A1, A2) в вашем коде не помогает. GHC должен скомпилировать ваш код, ожидая, что такой экземпляр будет объявлен позже, возможно, в других модулях (предположение об "открытом мире"). Это в значительной степени необходимо для обеспечения возможности отдельной компиляции.

    23.12.2015
    Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..