Почему эта функция, использующая класс типов с перекрывающимися экземплярами, ведет себя по-разному в GHCi? - PullRequest
3 голосов
/ 20 мая 2019

Фон

Я написал следующий код на Haskell (GHC 8.6.3):

{-# LANGUAGE 
  NoImplicitPrelude,
  MultiParamTypeClasses,
  FlexibleInstances, FlexibleContexts,
  TypeFamilies, UndecidableInstances,
  AllowAmbiguousTypes
#-}

import Prelude(Char, Show, show, undefined, id)

data Nil
nil :: Nil
nil = undefined

instance Show Nil where
  show _ = "nil"

data Cons x xs = Cons x xs 
  deriving Show

class FPack f r where
  fpack :: f -> r

instance {-# OVERLAPPABLE #-} f ~ (Nil -> r) => FPack f r where
  fpack f = f nil

instance (FPack (x -> b) r, f ~ (Cons a x -> b)) => FPack f (a -> r) where
  fpack f a = fpack (\x -> f (Cons a x))

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

Например, следующий

fpack id "a" "b" :: Cons [Char] (Cons [Char] Nil)

создает список Cons "a" (Cons "b" nil).

Задача

ВВ общем, я хочу вызвать fpack, передав id в качестве параметра f (как выше), поэтому я хочу определить следующую функцию в качестве краткой записи:

pack = fpack id

Если я загружу вышезапрограммируйте в GHCi и выполните указанную выше строку, pack определяется по желанию, а его тип (как указано :t) равен FPack (a -> a) r => r.Поэтому я определил функцию в своей программе следующим образом:

pack :: FPack (a -> a) r => r
pack = fpack id

Но при загрузке указанной программы в GHCi возникает следующая ошибка:

bugs\so-pack.hs:31:8: error:
    * Overlapping instances for FPack (a0 -> a0) r
        arising from a use of `fpack'
      Matching givens (or their superclasses):
        FPack (a -> a) r
          bound by the type signature for:
                     pack :: forall a r. FPack (a -> a) r => r
          at bugs\so-pack.hs:30:1-29
      Matching instances:
        instance [overlappable] (f ~ (Nil -> r)) => FPack f r
          -- Defined at bugs\so-pack.hs:24:31
        instance (FPack (x -> b) r, f ~ (Cons a x -> b)) =>
                 FPack f (a -> r)
          -- Defined at bugs\so-pack.hs:27:10
      (The choice depends on the instantiation of `a0, r')
    * In the expression: fpack id
      In an equation for `pack': pack = fpack id
   |
31 | pack = fpack id
   |     

Это приводит меня к моим вопросам.Почему эта функция работает, когда она определена в GHCi, а не в самой программе?Есть ли способ сделать эту работу в самой программе?Если да, то как?

Мои мысли

Из того, что я понимаю о GHC и Haskell, эта ошибка происходит из-за того, что pack может разрешиться в один из двух перекрывающихся экземпляров, иэто беспокоит GHC.Однако я подумал, что опция AllowAmbiguousTypes должна решить эту проблему, отложив выбор экземпляра до конечного сайта вызова.К сожалению, этого явно недостаточно.Мне любопытно, почему, но меня еще больше интересует, почему GHCi принимает это определение в цикле REPL, но не принимает его, когда оно находится внутри программы.

Касательный

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

Как видно изпример выше, т.е.

fpack id "a" "b" :: Cons [Char] (Cons [Char] Nil)

Я должен предоставить явную сигнатуру типа для fpack, чтобы она работала как нужно.Если я не предоставлю один (т.е. просто позвоню fpack id "a" "b"), GHCi выдаст следующую ошибку:

<interactive>:120:1: error:
    * Couldn't match type `Cons [Char] (Cons [Char] Nil)' with `()'
        arising from a use of `it'
    * In the first argument of `System.IO.print', namely `it'
      In a stmt of an interactive GHCi command: System.IO.print it

Можно ли как-нибудь изменить определение fpack, чтобы GHC мог вывестиправильная подпись типа?

1 Ответ

3 голосов
/ 20 мая 2019

Вам нужно создать экземпляр fpack вручную.

pack :: forall a r . FPack (a -> a) r => r
pack = fpack @(a->a) @r id

Для этого требуется ScopedTypeVariables, TypeApplications, AllowAmbiguousTypes.

В качестве альтернативы укажите тип для id.

pack :: forall a r . FPack (a -> a) r => r
pack = fpack (id :: a -> a)

Проблема в том, что GHC не может видеть, должен ли он использовать fpack, предоставленный этим ограничением FPack (a->a) r.Поначалу это может озадачивать, но учтите, что fpack (id :: T -> T) также может корректно генерировать r, если есть некоторые доступные instance FPack (T -> T) r.Поскольку id может быть как a -> a, так и T -> T (для любого T), GHC не может выбирать безопасно.

Это явление можно увидеть в ошибке типа, поскольку GHC упоминает a0,Эта переменная типа обозначает некоторый тип, который может быть a, но может быть и другим.Затем можно попытаться угадать, почему код не форсирует a0 = a, делая вид, что есть другие экземпляры, которые можно использовать вместо этого.

...