Есть ли автоматический способ запоминания глобальных полиморфных значений в Haskell?
Полиморфные "константы", такие как 5 :: Num a => a
, на самом деле не являются константами, а функциями словарного аргумента. Следовательно, если вы определяете
primes :: Num n => [n]
primes = ...
Плохой пример, конечно, нет веских оснований для того, чтобы он был полиморфным... что меня действительно интересует, если вы пытаетесь глобально сохранить мнимую нетривиальную полиморфную функцию, например. memo-trie
s.
то эта последовательность не будет передаваться между вызовами с разных сайтов, что не очень приятно с точки зрения производительности. (Разве это не основная причина, по которой стандарт Хаскелла благословил нас с Ограничением ограниченного мономорфизма?)
Единственный способ, которым я могу видеть, как обеспечить совместное использование, - это иметь мономорфный "тег", сидящий вокруг для каждого экземпляра сдерживающего класса. Например.
erastothenes :: Num n => [n]
erastothenes = ...
class (Num n) => HasPrimes n where
-- | @'primes' ≡ 'erastothenes'@
primes :: [n]
integerPrimes :: [Integer]
integerPrimes = erastothenes
instance HasPrimes Integer where
primes = integerPrimes
... что не приятно с точки зрения элегантности.
Есть ли лучший способ реализовать такую memoisation?
Ответы
Ответ 1
Если вы включите ConstraintKinds
и ExistentialQuantification
(или GADTs
), вы можете подтвердить типы словарей типа:
{-# LANGUAGE ConstraintKinds, ExistentialQuantification #-}
data Dict a = a => Dict
Если мы попробуем это,
fibs :: Num n => [n]
fibs = 1 : 1 : zipWith (+) fibs (drop 1 fibs)
fibs' :: [Integer]
fibs' = fibs
fibsWithDict :: Dict (Num n) -> [n]
fibsWithDict Dict = fs
where
fs = 1 : 1 : zipWith (+) fs (drop 1 fs)
fibs'' :: [Integer]
fibs'' = fibsWithDict Dict
в GHCi мы видим, что
λ> :set +s
λ>
λ> fibs !! 29
832040
(2.66 secs, 721235304 bytes)
λ>
λ> fibs !! 29
832040
(2.52 secs, 714054736 bytes)
λ>
λ>
λ> fibs' !! 29
832040
(2.67 secs, 713510568 bytes)
λ>
λ> fibs' !! 29
832040
(0.00 secs, 1034296 bytes)
λ>
λ>
λ> fibs'' !! 29
832040
(0.00 secs, 1032624 bytes)
Итак, fibs''
является единственной реализацией трех, которые немедленно запоминаются.
Обратите внимание, что мы должны сопоставить шаблон в конструкторе Dict
. В противном случае мы получим сообщение об ошибке n
, которое не будет иметь экземпляра Num
(например, вы ожидали бы, если наша подпись была просто fibsWithDict :: a -> [n]
).
Это полное решение, так как вы можете считать fibsWithDict Dict
выражением, которое мгновенно запоминается на любом типе, который вы на него набрасываете (если это экземпляр Num
). Например:
λ> (fibsWithDict Dict !! 29) :: Double
832040.0
(0.00 secs, 1028384 bytes)
EDIT: похоже, что эта явная передача в словаре не нужна здесь и может быть выполнена неявно с помощью ScopedTypeVariables
с локальной привязкой:
{-# LANGUAGE ScopedTypeVariables #-}
fibsImplicitDict :: forall a. Num a => [a]
fibsImplicitDict
= let fs :: [a]
fs = 1 : 1 : zipWith (+) fs (drop 1 fs)
in
fs
(Благодаря знаниям для понимания здесь!)
Ответ 2
Это довольно сложно по довольно техническим причинам. Классы типов открыты так, полиморфная константа не может во время компиляции обязательно "видеть", сколько типов удовлетворяют ограничению, поэтому он не может выделить много мономорфных трюков. С другой стороны, класс типа, конечно, не может видеть все возможные константы, которые он может сгенерировать, поэтому мономорфные thunks не могут быть выделены в словаре классов типов.
Вам нужно будет явно указать любые типы, в которых вы хотите выделить мономорфный thunk.
Ответ 3
Можно добавить ограничение Typeable
на n
и использовать другую таблицу напоминаний для каждого типа земли n
. Вероятно, вам придется использовать Dynamic
и cast
для этого, что является субоптимальным. Он также чувствует себя немного хакерским, тоже.
В зависимости от типизированного языка, конечно, можно моделировать карту (n : Num) -> [n]
, которая не требует casts
из Dynamic
. Возможно, что-то подобное может быть смоделировано с использованием GADT и какого-то механизма reification.