Вложенные неоднозначно типизированные методы

#haskell

#haskell

Вопрос:

Допустим, у меня есть класс

 class T where
    tag1 :: String
    tag2 :: String
  

С включенными неоднозначными типами я мог бы указать каждый из них в экземпляре:

 instance T A where
    tag1 = "tag1"
    tag2 = "tag2"
  

Если я хочу заставить tag2 добавить что-то к tag1 , я могу определить

 instance T A where 
    tag1 = "tag1"
    tag2 = tag1 @A    " suffix"
  

Это отлично работает, но если я хочу tag2 всегда добавлять suffix к каждому tag1 , мне, похоже, придется указывать неоднозначный вызов для каждого экземпляра.

Я понимаю необходимость этого, так как tag1 для каждого вызова будет работать любой экземпляр. Однако, есть ли какой-нибудь трюк в haskell, чтобы я мог указать его только один раз?

Что-то вроде

 tag2 :: T a => String
tag2 = tag1 @a    " suffix"
  

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

1. В нынешнем виде ваш код не компилируется, поскольку класс не имеет параметров.

Ответ №1:

Ваш код в настоящее время не компилируется, поскольку класс type не имеет параметров типа, поэтому я собираюсь предположить, что ваш код на самом деле (при условии, что AllowAmbiguousTypes включен)

 class T a where
    tag1 :: String
    tag2 :: String
  

Теперь вы можете предоставить реализацию по умолчанию для tag2 :

 class T a where
    tag1 :: String
    tag2 :: String
    tag2 = "tag2"
  

Но это не соответствует требованию добавления суффикса к tag1 .
Мы могли бы попробовать это (при условии, что TypeApplications включено):

 class T a where
    tag1 :: String
    tag2 :: String
    tag2 = tag1 @a    "suffix"
  

Теперь это не скомпилируется, и ошибка компиляции будет

 error: Not in scope: type variable `a'
  

и это справедливо, тип a нигде не определен. Однако мы хотим обратиться к a в заголовке класса, для этого нам нужно языковое расширение ScopedTypeVariables , и с его помощью код скомпилируется, и вы получите ожидаемые результаты (я предлагаю ознакомиться со связанной документацией)

Вот полная программа, демонстрирующая использование:

 {-# LANGUAGE TypeApplications, AllowAmbiguousTypes, ScopedTypeVariables #-}

class T a where
  tag1 :: String
  tag2 :: String
  tag2 = tag1 @a    " suffix"

data A = A
data B = B

instance T A where
  tag1 = "tagA"

instance T B where
  tag1 = "tagB"
  tag2 = "tagB overriden"

main = do
  putStrLn $ tag1 @A
  putStrLn $ tag2 @A
  putStrLn $ tag1 @B
  putStrLn $ tag2 @B
  

И результат таков:

 > ./foo
tagA
tagA suffix
tagB
tagB overriden
  

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

1. Я действительно допустил ошибку при вводе этого. Оба ответа были замечательными, но я приму ваш, поскольку полагаю, что вы ответили первым. Я не знал о языковом флаге

Ответ №2:

Да, вы можете сделать именно это — tag1 @a , но вам нужно внести две модификации: включить ScopedTypeVariables и добавить явный forall параметр, например:

 tag2 :: forall a. T a => String 
tag2 = tag1 @a    " suffix"
  

Явный forall — это то, что создает область видимости для переменной типа a , делая ее доступной во всем теле tag2 . Без этого (т. е. по стандартным правилам Haskell 2010) область действия переменной type ограничивается только сигнатурой типа и недоступна в теле.

Если вы хотели бы иметь tag2 в качестве метода класса, а не отдельной функции, вы можете добавить для него реализацию по умолчанию следующим образом:

 class T a where
    tag1 :: String

    tag2 :: String
    tag2 = tag1 @a    " suffix"
  

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