#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
, иначе области видимости вообще не будет.