Как я могу абстрагировать общий рекурсивный прикладной функтор 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, чтобы архивировать это, но это может стать хакерским, и вам нужна большая куча расширений для удовлетворения компилятора.