Как я могу абстрагировать общий рекурсивный прикладной функтор Haskell
при использовании прикладных функторов в Haskell я часто сталкивался с ситуациями, когда я заканчиваю с повторяющимся кодом, как это:
instance Arbitrary MyType where
arbitrary = MyType <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
в этом примере я хотел бы сказать:
instance Arbitrary MyType where
arbitrary = applyMany MyType 4 arbitrary
но я не могу понять, как сделать applyMany
(или что-то похожее на него). Я даже не могу понять, каким будет тип, но для этого потребуется конструктор данных, Int (n), и функция для применения n
6 ответов
Я думаю, вы можете сделать это с помощью OverlappingInstances hack:
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, TypeFamilies, OverlappingInstances #-}
import Test.QuickCheck
import Control.Applicative
class Arbitrable a b where
convert :: Gen a -> Gen b
instance (Arbitrary a, Arbitrable b c) => Arbitrable (a->b) c where
convert a = convert (a <*> arbitrary)
instance (a ~ b) => Arbitrable a b where
convert = id
-- Should work for any type with Arbitrary parameters
data MyType a b c d = MyType a b c d deriving (Show, Eq)
instance Arbitrary (MyType Char Int Double Bool) where
arbitrary = convert (pure MyType)
check = quickCheck ((\s -> s == s) :: (MyType Char Int Double Bool -> Bool))
проверить вывести. Любая другая хорошая библиотека дженериков должна быть в состоянии сделать это; derive-это только тот, с которым я знаком. Например:
{-# LANGUAGE TemplateHaskell #-}
import Data.DeriveTH
import Test.QuickCheck
$( derive makeArbitrary ''MyType )
чтобы ответить на вопрос, который вы на самом деле задали, FUZxxl прав, это невозможно в простом vanilla Haskell. Как вы указываете,неясно, каким должен быть его тип. Возможно с шаблоном Haskell метапрограммирование (не слишком приятное). Если вы идете по этому маршруту, вы, вероятно, должны просто использовать библиотека дженериков, которая уже провела для вас тяжелое исследование. Я считаю, что это также возможно с использованием натуралов уровня типа и typeclasses, но, к сожалению, такие решения уровня типа обычно трудно абстрагироваться. Конор Макбрайд работа над этой проблемой.
вот что я получил по крайней мере:
{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
module ApplyMany where
import Control.Applicative
import TypeLevel.NaturalNumber -- from type-level-natural-number package
class GetVal a where
getVal :: a
class Applicative f => ApplyMany n f g where
type Res n g
app :: n -> f g -> f (Res n g)
instance Applicative f => ApplyMany Zero f g where
type Res Zero g = g
app _ fg = fg
instance
(Applicative f, GetVal (f a), ApplyMany n f g)
=> ApplyMany (SuccessorTo n) f (a -> g)
where
type Res (SuccessorTo n) (a -> g) = Res n g
app n fg = app (predecessorOf n) (fg<*>getVal)
пример использования:
import Test.QuickCheck
data MyType = MyType Char Int Bool deriving Show
instance Arbitrary a => GetVal (Gen a) where getVal = arbitrary
test3 = app n3 (pure MyType) :: Gen MyType
test2 = app n2 (pure MyType) :: Gen (Bool -> MyType)
test1 = app n1 (pure MyType) :: Gen (Int -> Bool -> MyType)
test0 = app n0 (pure MyType) :: Gen (Char -> Int -> Bool -> MyType)
кстати, я думаю, что это решение не очень полезно в реальном мире. Особенно без локальных типов-классов.
не удовлетворенный моим другим ответом, я придумал более удивительный.
-- arb.hs
import Test.QuickCheck
import Control.Monad (liftM)
data SimpleType = SimpleType Int Char Bool String deriving(Show, Eq)
uncurry4 f (a,b,c,d) = f a b c d
instance Arbitrary SimpleType where
arbitrary = uncurry4 SimpleType `liftM` arbitrary
-- ^ this line is teh pwnzors.
-- Note how easily it can be adapted to other "simple" data types
ghci> :l arb.hs
[1 of 1] Compiling Main ( arb.hs, interpreted )
Ok, modules loaded: Main.
ghci> sample (arbitrary :: Gen SimpleType)
>>>a bunch of "Loading package" statements<<<
SimpleType 1 'B' False ""
SimpleType 0 '\n' True ""
SimpleType 0 '6' False "8! 7"
...
длинное объяснение того, как я это понял
Итак, вот как я его получил. Мне было интересно: "ну как уже есть Arbitrary
экземпляр (Int, Int, Int, Int)
? Я уверен, что никто писал это, поэтому его нужно как-то вывести. Конечно, я нашел следующее в документы для экземпляров Произвольный:
(Arbitrary a, Arbitrary b, Arbitrary c, Arbitrary d) => Arbitrary (a, b, c, d)
Ну, если они уже это определили, то почему бы не злоупотреблять этим? Простые типы, состоящие только из меньших произвольных типов данных, мало чем отличаются от кортежа.
Итак, теперь мне нужно как-то преобразовать "произвольный" метод для 4-кортежа, чтобы он работал для моего типа. Uncurrying является, вероятно, участвует.
остановить. Время Hoogle!
(мы можем легко определить наш собственный uncurry4
, так что предположим, что мы уже придется оперировать этим.)
у меня есть генератор, arbitrary :: Gen (q,r,s,t)
(где q,r,s, t-все экземпляры произвольных). Но давайте просто скажем, что это arbitrary :: Gen a
. Другими словами,a
представляет (q,r,s,t)
. У меня есть функция, uncurry4
, который имеет тип (q -> r -> s -> t -> b) -> (q,r,s,t) -> b
. Мы, очевидно, собираемся применить uncurry4 к нашему SimpleType
конструктор. Так что uncurry4 SimpleType
типа (q,r,s,t) -> SimpleType
. Однако давайте сохраним возвращаемое значение generic, потому что Хугл не знает о нашем SimpleType. Поэтому вспомним наше определение a
, то есть по существу uncurry4 SimpleType :: a -> b
.
я получил Gen a
и a -> b
. И я хочу Gen b
результат. (Помните, для нашей ситуации, a
и (q,r,s,t)
и b
is SimpleType
). Поэтому я ищу функцию с сигнатурой этого типа:Gen a -> (a -> b) -> Gen b
. Hoogling, что, и зная, что Gen
пример Monad
, я сразу же признать liftM
как монадико-магическое решение моих проблем.
Hoogle сохраняет снова день. Я знал, что, вероятно, был какой-то "подъемный" комбинатор, чтобы получить желаемый результат, но я честно не думал использовать liftM (durrr!) пока я hoogled подписью типа.
проверить liftA2 и liftA3. Кроме того, вы можете легко написать свой собственный applyTwice или applyThrice методы, как так:
applyTwice :: (a -> a -> b) -> a -> b
applyTwice f x = f x x
applyThrice :: (a -> a -> a -> b) -> a -> b
applyThrice f x = f x x x
Я не вижу простого способа получить общее applyMany, которое вы просите, но писать тривиальных помощников, таких как эти, не сложно и не редко.
[edit] Итак, оказывается, вы думаете, что что-то вроде этого будет работать
liftA4 f a b c d = f <$> a <*> b <*> c <*> d
quadraApply f x = f x x x x
data MyType = MyType Int String Double Char
instance Arbitrary MyType where
arbitrary = (liftA4 MyType) `quadraApply` arbitrary
но это не так. (liftA4 MyType)
имеет подпись типа (Applicative f) => f Int -> f String -> f Double -> f Char -> f MyType
. Это несовместимо с первым параметром quadraApply, который имеет сигнатуру типа (a -> a -> a -> a -> b) -> a -> b
. Это будет работать только для структур данных, которые содержат несколько значений произвольного типа.
data FourOf a = FourOf a a a a
instance (Arbitrary a) => Arbitrary (FourOf a) where
arbitrary = (liftA4 FourOf) `quadraApply` arbitrary
ghci> sample (arbitrary :: Gen (FourOf Int))
конечно, вы могли бы просто сделать это, если бы у вас была такая ситуация
ghci> :l +Control.Monad
ghci> let uncurry4 f (a, b, c, d) = f a b c d
ghci> samples <- sample (arbitrary :: Gen (Int, Int, Int, Int))
ghci> forM_ samples (print . uncurry4 FourOf)
может быть какая-то языковая ПРАГМА, которая может встроить "произвольную" функцию в более разнообразные типы данных. Но в настоящее время это выходит за рамки моего уровня Haskell-fu.
Это невозможно с Haskell. Проблема в том, что ваша функция будет иметь тип, который зависит от числового аргумента. С системой типов, которая позволяет зависимые типы, это должно быть возможно, но я думаю, не в Haskell.
Что вы можете попробовать, это использовать полиморфизм и tyeclasses, чтобы архивировать это, но это может стать хакерским, и вам нужна большая куча расширений для удовлетворения компилятора.