Можно ли определить класс типов в Haskell таким образом, чтобы функция могла выполнять определенное поведение независимо от количества параметров? Например:
{-# LANGUAGE FlexibleInstances #-}
class FillZeroes ... where
...
f :: Num a => a -> a -> a
f x y = x+y+1
g :: Num a => a -> a
g x = 10*x
h :: (Eq a,Num a) => a -> Bool
h 0 = False
h _ = True
fillZeroes f === f 0 0 === 1
fillZeroes g === g 0 === 0
fillZeroes h === h 0 === False
«Возможно ли определить класс типов в Haskell таким образом, чтобы функция могла выполнять определенное поведение независимо от количества параметров?» Да, см., например. Класс Testable типа QuickCheck для примера.
Хотя это возможно, я бы не рекомендовал делать это с вариативным обманом. Я думаю, что вы действительно должны использовать, например. f :: (Double,Double) -> Double, g :: Double -> Double, h :: Int -> Bool, и тогда вы можете оценить их все как f zeroV/g zeroV/h zeroV с zeroV::AdditiveGroup v => v.





Функции с переменным числом, конечно, возможны, но «заполнение» нулями немного сложно. Основная причина этого в том, что GHC на самом деле не знает, сколько нужно заполнить! Возьми свою функцию f. Вам может показаться очевидным, что «заполнение нулями» означает применение 0 к f дважды, но вы уверены? Что касается GHC, возможно, вы хотите, чтобы ваш результат был функцией из a -> a. Или, может быть, вы создали у функций типа Int -> Int экземпляр Num — тогда, возможно, fillZeros следует сначала предоставить два аргумента 0 :: Int -> Int для f, а затем еще и 0 :: Int. Есть так много вариантов!
По сути, это сводится к тому, что вы можете создать функцию fillZeros, но вам обязательно понадобятся аннотации типов, чтобы делать с ней что-то полезное. С этой оговоркой приступим.
Хорошая начальная попытка для класса:
{-# LANGUAGE MultiParamTypeClasses #-}
class FillZeroes x y where
fillZeroes :: x -> y
instance (Num a, FillZeroes b c) => FillZeroes (a -> b) c where
fillZeroes f = fillZeroes (f 0)
Здесь fillZeroes — это функция, которая принимает x и производит y, и мы создали экземпляр, где x — это тип функции a -> b (где Num a). Обратите внимание, как это выглядит рекурсивно, с ограничением FillZeroes b c и вызовом fillZeroes (f 0) в реализации. Если это рекурсивный случай, то, очевидно, нам понадобится базовый случай. Как мы можем написать это? Самый простой вариант — использовать перекрывающийся экземпляр:
instance {-# OVERLAPPABLE #-} FillZeroes a a where
fillZeroes = id
Это говорит о том, что если никакие другие экземпляры не применимы (т. Е. Если тип x не является функцией, которая может принимать число), то больше ничего не делайте. Давайте посмотрим, что произойдет (обратите внимание, что удаление аннотации любого типа вызовет ошибку):
{-# LANGUAGE TypeApplications #-}
> fillZeroes (f @Int) :: Int
1
> fillZeroes (f @Int 4) :: Int
5
> fillZeroes (g @Int) :: Int
0
> fillZeroes (h @Int) :: Bool
False
Можем ли мы сделать лучше? Почему нам нужно сообщать GHC как конкретный тип функции (что мы делаем с помощью приложения типа @Int), так и тип результата? Проблема в том, что GHC необходимо точно знать оба типа x и y, чтобы получить правильные экземпляры классов типов. Как оказалось, мы можем использовать семейства типов, чтобы обойти это препятствие. Рассмотрим следующее семейство типов:
{-# LANGUAGE TypeFamilies #-}
type family FZResult a where
FZResult (a -> b) = FZResult b
FZResult a = a
Это (закрытое) семейство типов дает тип результата, который мы ищем. Итак, мы можем добавить это в наш класс вместо параметра y:
class FillZeroes a where
fillZeroes :: a -> FZResult a
instance {-# OVERLAPPABLE #-} FZResult a ~ a => FillZeroes a where
fillZeroes = id
instance (Num b, FillZeroes a) => FillZeroes (b -> a) where
fillZeroes f = fillZeroes (f 0)
Теперь нам нужно только сообщить GHC, каков конкретный тип аргумента, и он может использовать семейство типов, чтобы определить, сколько нулей нужно заполнить:
> fillZeroes (f @Int)
1
> fillZeroes (f @Int 4)
5
> fillZeroes (g @Int)
0
> fillZeroes (h @Int)
False
Должно быть возможно, да. Вы изучали реализацию других функций с переменным числом аргументов в Haskell? Думали ли вы, что должен предоставлять ваш класс типов
FilleZeroes? Вы пытались реализовать экземпляр для его функций?