Добавить сигнатуру типа для вспомогательной функции внутри - PullRequest
0 голосов
/ 31 мая 2019

Сначала я определю следующий тип данных

data Supply s a = S (Stream s -> (a, Stream s))
data Stream a = Cons a (Stream a)

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

mapSupply :: (a -> b) -> Supply s a -> Supply s b

А вот моя реализация: (которая без проблем компилируется)

mapSupply :: (a -> b) -> Supply s a -> Supply s b
mapSupply mapFunc (S supFuncA) = S supFuncB where
    supFuncB strm = let (la, strms) = supFuncA strm in 
        ((mapFunc la), strms)

Затем я столкнулся с проблемой, когда попытался записать сигнатуру типа для вспомогательной функции с именем supFuncB, которую я определил внутри mapSupply.

Сигнатура типа для supFuncB очень проста и должна быть:

supFuncB :: Stream s -> (b, Stream s)

Однако, когда я попытался добавить сигнатуру типа в код, я получил ошибку компилятора. Код выглядит так

mapSupply :: (a -> b) -> Supply s a -> Supply s b
mapSupply mapFunc (S supFuncA) = S supFuncB where
    supFuncB :: Stream s -> (b, Stream s)
    supFuncB strm = let (la, strms) = supFuncA strm in 
        ((mapFunc la), strms)

А потом компилятор пожаловался:

• Couldn't match type ‘s1’ with ‘s’
  ‘s1’ is a rigid type variable bound by
    the type signature for:
      supFuncB :: forall s1 b1. Stream s1 -> (b1, Stream s1)
    at Main.hs:58:5-41
  ‘s’ is a rigid type variable bound by
    the type signature for:
      mapSupply :: forall a b s. (a -> b) -> Supply s a -> Supply s b
    at Main.hs:56:1-49
  Expected type: Stream s1
    Actual type: Stream s
• In the expression: strms
  In the expression: ((mapFunc la), strms)
  In the expression:
    let (la, strms) = supFuncA strm in ((mapFunc la), strms)

Я очень новичок в Haskell и не понимаю, почему компиляция не удалась? И какая должна быть правильная сигнатура типа, если бы я добавил ее в код.

1 Ответ

0 голосов
/ 01 июня 2019

Чтобы начать с конца, решение включает ScopedTypeVariables и использует явный forall в подписи mapSupply, например:

{-# LANGUAGE ScopedTypeVariables #-}  -- Put this at the top of your file.

mapSupply :: forall a b s. (a -> b) -> Supply s a -> Supply s b
mapSupply mapFunc (S supFuncA) = S supFuncB where
    supFuncB :: Stream s -> (b, Stream s)
    supFuncB strm = let (la, strms) = supFuncA strm in 
        ((mapFunc la), strms)

Ниже приведено объяснение того, почемуэто необходимо.


Когда вы пишете такую ​​подпись:

mapSupply :: (a -> b) -> Supply s a -> Supply s b

GHC фактически видит это:

mapSupply :: forall a b s. (a -> b) -> Supply s a -> Supply s b

forall, чтообычно может быть неявным, говорит, что a, b и s могут быть чем угодно - mapSupply - это полиморфная функция, и поэтому любой, кто ее использует, может свободно выбирать любые конкретные типыдля трех типов переменных.Если явно указать forall, второе определение будет выглядеть следующим образом:

mapSupply :: forall a b s. (a -> b) -> Supply s a -> Supply s b
mapSupply mapFunc (S supFuncA) = S supFuncB where
    supFuncB :: forall s b. Stream s -> (b, Stream s)
    supFuncB strm = let (la, strms) = supFuncA strm in 
        ((mapFunc la), strms)

В соответствии с этим a, b и s в mapSupply могут быть чем угодно, аТо же самое касается s и b в supFuncB.Это проблема, хотя.Например, определение включает strms, тип которого s ... за исключением того, что s, который отображается, потому что вы используете supFuncA, равен , а не тот из supFuncB подпись, а точнее та, что из mapSupply подписи.Хотя s из mapSupply, в принципе, может быть чем угодно, как я уже отмечал ранее, как только вы действительно выберете s с помощью mapSupply, s из supFuncB должно совпадать с ним.В этом случае forall в сигнатуре supFuncB неуместен, поскольку переменные его типа не могут быть ничем.Становится легче увидеть, если мы переименуем переменные типа из supFuncB, чтобы их имена не конфликтовали с именами из mapSupply (учитывая forall, это должно быть допустимым шагом для этого):

mapSupply :: forall a b s. (a -> b) -> Supply s a -> Supply s b
mapSupply mapFunc (S supFuncA) = S supFuncB where
    supFuncB :: forall s1 b1. Stream s1 -> (b1, Stream s1)
    supFuncB strm = let (la, strms) = supFuncA strm in 
        ((mapFunc la), strms)

(GHC делает это внутренне, что объясняет, почему в сообщении об ошибке вы указали переменную типа s1.)

Эта проблема возникла только из-за подписи, добавленной к supFuncB, котораяввел неявное forall.Без подписи GHC делает то, что вы хотите, не обобщая типы из supFuncB - в этом случае это не полиморфно, а скорее мономорфно в переменных типа a, b и s, которые позаимствованы у mapSupply.Расширение ScopedTypeVariables позволяет восстановить это поведение при записи сигнатуры типа для supFuncB.Когда он включен, использование явного forall для переменных типа в сигнатуре приведет к тому, что любые переменные типа с тем же именем в соответствующем определении относятся к одной и той же вещи (если только они не находятся под forallв собственных подписях).Другими словами, благодаря этому становится возможным ссылаться на переменные из внешней сигнатуры в любом месте области действия соответствующего определения, что оправдывает название расширения.

...