Лень / строгость между данными и 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 ответа:
Как вы, вероятно, знаете, основное различие между
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
строго и принудительно. Anewtype
, с другой стороны, он представлен точно так же, как и тип, который обертывает его конструктор. Поэтому сопоставление в конструкторе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
значение, которое сопоставляется. Это происходит с containedundefined
, но это не имеет значения, так как мы не используем его-мы только смотрим на самый верхний конструктор.
В дополнение ко всем ссылкам, которые вы дали, вы можете найти эту статью актуальной.