Реализация метода по умолчанию, зависящего от типа переменной, в Haskell - PullRequest
1 голос
/ 05 апреля 2019

Я пытаюсь определить реализации метода по умолчанию, но только если переменные типа класса порождают некоторые другие классы.

Я попытался создать экземпляры, зависящие от типа, используя => (правильно ли я его использую?), но я получаю «ошибку объявления дублирующегося экземпляра»: (https://repl.it/@solly_ucko/Distributions)

{-# LANGUAGE FlexibleInstances, FunctionalDependencies, MultiParamTypeClasses, InstanceSigs #-}

import Data.Int
import Data.Ratio
import Data.Set
import System.Random

duplicate :: a -> (a, a)
duplicate a = (a, a)

listRange :: Enum a => a -> a -> [a]
listRange a b = [a..b]

class Fractional w => Distribution d v w where
    probability :: d v w -> v -> w

    probabilityOfRange :: Ord v => d v w -> v -> v -> w

    ranges :: (Ord v) => d v w -> Set (v, v)
    ranges = (Data.Set.map duplicate) . values

    sample :: RandomGen g => d v w -> g -> (v, g)
    --sample d g = (scanl1 (+) $ flip Prelude.map $ probability d, g) -- Will need to implement some sort of binary tree, most likely.

    sampleIO :: d v w -> IO v
    sampleIO = getStdRandom . sample

    values :: d v w -> Set v

instance (Ord v, Fractional w) => Distribution d v w where
    probability d v = probabilityOfRange d v v

instance Enum v => Distribution d v w where
    probabilityOfRange d v1 v2 = sum $ Prelude.map (probability d) [v1..v2]

instance (Enum v, Ord v) => Distribution d v w where
    values = fromList . (concatMap $ uncurry listRange) . toList . ranges

Когда я пытаюсь добавить реальные экземпляры (и закомментировать некоторые из «экземпляров», которые я создал ранее, чтобыкомпилятор может достичь этой точки), он выдает мне ошибку о конфликтующих экземплярах.

data Empty v w = Empty

instance Distribution Empty v (Ratio Int8) where
    sample _ g = (undefined, g)
    sampleIO _ = return undefined
    probabilityOfRange _ _ _ = 0
    values _ = empty

data Singleton v w = Singleton v

instance Distribution Singleton v Integer where
    sample (Singleton v) g = (v, g)
    sampleIO (Singleton v) = return v
    probabilityOfRange (Singleton v1) v2 v3
        | v2 <= v1 && v1 <= v3 = 1
        | otherwise        = 0

data Uniform v w = Uniform (Set v)

Чтобы уточнить, моя цель состоит в том, чтобы probability и values были определены для всех Distributions, а дляprobabilityOfRange должен быть определен для всех Distributions со значениями, получающими Ord. Я также хотел бы предоставить значения по умолчанию при выполнении дополнительных ограничений, потому что без них разумное значение по умолчанию (основанное на других методах) невозможно.

1 Ответ

4 голосов
/ 05 апреля 2019

Попытка указать значение по умолчанию для одного метода, используя что-то вроде:

instance (Ord v, Fractional w) => Distribution d v w where
    probability d v = probabilityOfRange d v v

не будет работать. Экземпляры на Haskell не «накапливаются». Для данной тройки типов d v w будет применяться не более одного условия instance Distribution d v w. (Если из-за «перекрывающихся» экземпляров могут применяться несколько предложений , существуют механизмы для выбора «наилучшего» соответствия, но нет прямых механизмов для объединения методов из нескольких предложений экземпляров.)

В общем, если у вас есть метод класса:

class Distribution d v w where
    probability :: d v w -> v w

и вы хотите определить метод по умолчанию с более строгой сигнатурой типа (т. Е. С ограничениями для некоторых типов):

probability :: (Ord v) => d v w -> v -> w
probability d v = probabilityOfRange d v v

Есть два подхода.

Первый - использовать расширение DefaultSignatures. Это позволяет отделить сигнатуру типа для метода от (возможно, более строгой) сигнатуры типа метода default . Синтаксис:

class Fractional w => Distribution d v w where
    probability :: d v w -> v -> w
    default probability :: (Ord v) => d v w -> v -> w
    probability d v = probabilityOfRange d v v
    ...

Предостережение заключается в том, что если вы определяете экземпляр, который не переопределяет метод по умолчанию, то он должен удовлетворять ограничению Ord v, иначе он не будет проверять тип.

Если вам нужен больший контроль над использованием метода по умолчанию, тогда обычный подход состоит в том, чтобы разделить определение по умолчанию на отдельную функцию, которая должна быть явно включена в экземпляры, которые хотят его использовать. Итак, у вас будет:

class Fractional w => Distribution d v w where
    probability :: d v w -> v -> w

probabilityDefault :: (Distribution d v w, Ord v) => d v w -> v -> w
probabilityDefault d v = probabilityOfRange d v v

и экземпляр, который хочет использовать значение по умолчанию, должен сделать это явно:

instance Distribution Whatever Int w where
    probability = probabilityDefault
...