2011-01-21 10 views
7

При использовании аппликативных функторов в 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 раз. Это происходит при создании экземпляров для QuickCheck, SmallCheck, Data.Binary, Xml-сериализации и других рекурсивных ситуаций.

Так как я могу определить applyMany?

ответ

6

Я думаю, что вы можете сделать это с OverlappingInstances взломать:

{-# 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)) 
+1

отличный 'конвертировать' взломать! Я думаю, вы можете избавиться от OverlappingInstances, сместив количество «произвольных» приложений с номером типа (как и я). –

2

Это невозможно с Haskell. Проблема в том, что ваша функция будет иметь тип, зависящий от числового аргумента. С системой типа, которая позволяет зависимые типы, это должно быть возможно, но я думаю, что не в Haskell.

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

+2

Возможно, это можно записать с помощью рисунка Дэниэла Фридлендера и Миа Индрики? («N-ary zipWith in Haskell» - http://www.brics.dk/RS/98/38/BRICS-RS-98-38.pdf). Этот образец иногда используется в дикой природе, но лично я предпочитаю семейство arity (более шаблонный, но более простой). –

10

Отъезд derive. Любая другая хорошая библиотека дженериков должна быть в состоянии сделать это; получить только тот, с которым я знаком. Например:

{-# LANGUAGE TemplateHaskell #-} 
import Data.DeriveTH 
import Test.QuickCheck 

$(derive makeArbitrary ''MyType) 

Чтобы решить вопрос, который вы на самом деле спросил, FUZxxl прав, это не представляется возможным в простом ванильным Haskell. Как вы указываете, неясно, каков должен быть его тип. Это возможно при метапрограммировании шаблона Хаскелла (не слишком приятного). Если вы идете по этому маршруту, вы, вероятно, должны просто использовать библиотеку дженериков, которая уже сделала для вас тяжелое исследование. Я считаю, что это также возможно с использованием натуры типа и типов, но, к сожалению, такие решения на уровне типовых типов обычно трудно абстрагироваться. Конор Макбрайд - working on that problem.

+0

Возможно, это возможно с помощью варгала Олега: http://okmij.org/ftp/Haskell/vararg-fn.lhs? –

+0

Возможно. Я пытаюсь предложить решения, которые не являются гигантскими хаками, поэтому при необходимости они могут быть обобщены. – luqui

4

Отъезд liftA2 and 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 вы просите, но писать тривиальные помощник, такие как они ни трудно и не редкость.


[править] Таким образом, получается, можно подумать, что что-то подобное будет работать

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) 

Там может быть некоторый язык Прагма, которые могут впихнуть в «произвольную» функцию в более разнообразных типов данных. Но это в настоящее время выходит за рамки моего уровня Хаскелл-фу.

+0

Вдохновленный моей работой здесь, особенно сессия ghci ближе к концу, я создал еще один, удивительный ответ. –

5

Вот что I'v есть по крайней мере:

{-# 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) 

Btw, Я думаю, что это решение не очень полезно в реальном мире. Особенно без местных типов.

+0

Что делать, если мы хотим применить его к MyType Char Int Bool? –

+0

@ Даниэль, кажется, что это не так просто (если возможно) сделать это в общем виде (без зависимости от произвола). В случае успеха я дам ему еще одну попытку и отправлю решение. –

+0

@ Даниэль, я только что обновил свой ответ с более общим решением. Здесь используется экземпляр класса «GetVal», вместо того, чтобы «произвольно» использовать аргумент 'apply'. –

5

Не удовлетворенным моим другим ответом, я придумал удивительный вариант.

-- 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 '\186' False "\208! \227" 
... 

Продолжительное объяснение того, как я понял это

Так вот, как я получил его. ?. Я задавался вопросом, «а как там уже Arbitrary экземпляр для (Int, Int, Int, Int) Я уверен, что никто не писал, поэтому оно должно быть получено каким-то образом Конечно же, я нашел следующее в docs for instances of Arbitrary:

(Arbitrary a, Arbitrary b, Arbitrary c, Arbitrary d) => Arbitrary (a, b, c, d) 

Ну, если у них уже есть, что определено, то почему бы не злоупотреблять? Простые типы, которые просто состоят из небольших произвольных типов данных, которые не сильно отличаются от всего кортежа.

Так что теперь мне нужно как-то преобразование " произвольный "метод для 4-кортежей, так что он работает для моего типа. Возможно, будет задействован немедленный запуск.

Остановить. Время в Hoogle!

(Мы можем легко определить наши собственные uncurry4, поэтому предположим, что мы уже имеем это работать с.)

У меня есть генератор, arbitrary :: Gen (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. Однако сохраним возвращаемое значение родовым, потому что Hoogle не знает о нашем SimpleType. Поэтому, помня о нашем определении a, мы имеем по существу uncurry4 SimpleType :: a -> b.

У меня есть Gen a и функция a -> b. И я хочу получить результат Gen b.(Помните, что для нашей ситуации a - (q,r,s,t) и b - SimpleType). Поэтому я ищу функцию с этим типом подписи: Gen a -> (a -> b) -> Gen b. Hoogling that, и зная, что Gen является примером Monad, я сразу же признаю liftM в качестве монадически-магического решения моих проблем.

Hoogle сохраняет этот день снова. Я знал, что, вероятно, есть какой-то «подъемный» комбинатор, чтобы получить желаемый результат, но я, честно говоря, не думал использовать liftM (durrr!) До тех пор, пока не подхватил подпись типа.

+5

Использование экземпляров для «основных» типов, таких как кортежи для кодирования экземпляров для вещей, похожих на кортеж, путем проявления изоморфизма, не является «злоупотреблением». ;) – nomen