Лень / строгость между данными и newtype


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

В первом примере используется data (при условии правильного применения экземпляра):

data Parser t a = Parser {
        getParser ::  [t] -> Maybe ([t], a) 
    }

> getParser (pure (,) <*> literal ';' <*> undefined ) "abc"
*** Exception: Prelude.undefined

Второй использует newtype. Другой разницы нет:

newtype Parser t a = Parser {
        getParser ::  [t] -> Maybe ([t], a) 
    }

> getParser (pure (,) <*> literal ';' <*> undefined ) "abc"
Nothing

literal x синтаксический анализатор, который успешно использует один маркер ввода, если его аргумент совпадает с первым маркером. Поэтому в этом примере он не работает, так как ; не соответствует a. Однако, пример data по-прежнему видит, что следующий синтаксический анализатор не определен, в то время как пример newtype этого не делает.

Я читал это, это , и это, но не понимаю их достаточно хорошо, чтобы понять, почему первый пример не определен. Мне кажется, что в этом примере newtype является более ленивым, чем data, что противоположно тому, что было сказано в ответах. (По крайней мере, еще один человек был смущен этим тоже).

Почему происходит переключение с data Чтобы newtype изменить определенность этого примера?


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

instance Applicative (Parser s) where
  Parser f <*> Parser x = Parser h
    where
      h xs = 
        f xs >>= (ys, f') -> 
        x ys >>= (zs, x') ->
        Just (zs, f' x')

  pure a = Parser (xs -> Just (xs, a))

В то время как с этим экземпляром парсер data выше делает не вывод undefined (предполагая правильный экземпляр монады для Parser s):

instance Applicative (Parser s) where
  f <*> x =
      f >>= f' ->
      x >>= x' ->
      pure (f' x')

  pure = pure a = Parser (xs -> Just (xs, a))

Полный фрагмент кода:

import Control.Applicative
import Control.Monad (liftM)

data Parser t a = Parser {
        getParser ::  [t] -> Maybe ([t], a) 
    }


instance Functor (Parser s) where
  fmap = liftM

instance Applicative (Parser s) where
  Parser f <*> Parser x = Parser h
    where
      h xs = f xs >>= (ys, f') -> 
        x ys >>= (zs, x') ->
        Just (zs, f' x')

  pure = return


instance Monad (Parser s) where
  Parser m >>= f = Parser h
    where
      h xs =
          m xs >>= (ys,y) ->
          getParser (f y) ys

  return a = Parser (xs -> Just (xs, a))


literal :: Eq t => t -> Parser t t
literal x = Parser f
  where
    f (y:ys)
      | x == y = Just (ys, x)
      | otherwise = Nothing
    f [] = Nothing
2 15

2 ответа:

Как вы, вероятно, знаете, основное различие между data и newtype состоит в том, что с data конструкторы ленивы, а newtype строги, т. е. заданы следующие типы

data    D a = D a 
newtype N a = N a

Затем D ⊥ `seq` x = x, но N ⊥ `seq` x = ⊥.

Что, возможно, менее широко известно, однако, когда вы сопоставляете шаблон на этих конструкторах, роли "поменялись местами", т. е. с

constD x (D y) = x
constN x (N y) = x

Затем constD x ⊥ = ⊥, но constN x ⊥ = x.

Вот что происходит в вашей жизни. образец.

Parser f <*> Parser x = Parser h where ...

С data, совпадение паттерна в определении <*> будет немедленно расходиться, если либо аргументов , но с newtype конструкторы игнорируются, и это так же, как если бы вы написали

f <*> x = h where

, которые будут расходиться только для x = ⊥, Если требуется x.

Разница между data и newtype заключается в том, что data "поднят", а newtype Нет. это означает, что data имеет дополнительное ⊥ - в этом случае это означает, что undefined /= Parser undefined. Когда ваш Applicative шаблон кода совпадает с Parser x, он вызывает значение , Если конструктор.

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

λ> data Foo = Foo Int deriving Show
λ> case undefined of Foo _ -> True
*** Exception: Prelude.undefined

Таким образом, сопоставление шаблонов в конструкторе data строго и принудительно. A newtype, с другой стороны, он представлен точно так же, как и тип, который обертывает его конструктор. Поэтому сопоставление в конструкторе newtype абсолютно ничего не делает:

λ> newtype Foo = Foo Int deriving Show
λ> case undefined of Foo _ -> True
True

Есть, вероятно, два способа изменить вашу программу data таким образом, чтобы она не рухнула. Одним из них было бы использование неопровержимого соответствия шаблону в вашем экземпляре Applicative, который всегда будет "успешным" (но использование сопоставленных значений в любом месте позже может потерпеть неудачу). Каждое newtype совпадение ведет себя как неопровержимая закономерность (поскольку нет конструктор, чтобы соответствовать, строго говоря).

λ> data Foo = Foo Int deriving Show
λ> case undefined of ~(Foo _) -> True
True

Другой вариант-использовать Parser undefined вместо undefined:

λ> case Foo undefined of Foo _ -> True
True

Это совпадение будет успешным, потому что есть действительное Foo значение, которое сопоставляется. Это происходит с contained undefined, но это не имеет значения, так как мы не используем его-мы только смотрим на самый верхний конструктор.


В дополнение ко всем ссылкам, которые вы дали, вы можете найти эту статью актуальной.