簡體   English   中英

我如何抽象一個常見的Haskell遞歸應用函子模式

[英]How can I abstract a common Haskell recursive applicative functor pattern

在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

簽出派生 任何其他好的泛型庫也應該能夠做到這一點; 派生只是我熟悉的那個。 例如:

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

$( derive makeArbitrary ''MyType )

為了解決您實際提出的問題,FUZxxl是正確的,這在純香草Haskell中是不可能的。 如您所指出的,尚不清楚它的類型應該是什么。 Template Haskell元編程是可能的(不太愉快)。 如果走那條路,您可能應該只使用已經為您進行了艱苦研究的泛型庫。 我相信也可以使用類型級別的自然類型和類型類,但是不幸的是,此類類型級別的解決方案通常很難抽象出來。 Conor McBride 正在研究這個問題

我認為您可以使用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))

對其他答案不滿意,我想出了一個很棒的答案。

-- 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"
...

關於我如何解決的冗長解釋

所以這就是我的方法。 我想知道,“那么(Int, Int, Int, Int)已經有一個Arbitrary實例了嗎?我確定沒有人寫過它,因此必須以某種方式派生。當然,我在文檔中找到了以下內容對於任意情況

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

好吧,如果他們已經定義好了,那為什么不濫用它呢? 僅由較小的任意數據類型組成的簡單類型與元組沒有太大區別。

因此,現在我需要以某種方式將4元組的“任意”方法轉換為適用於我的類型的方法。 可能會涉及到無憂無慮。

停止。 笨拙的時間!

(我們可以輕松定義自己的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 不過,讓我們保持返回值的通用性,因為Hoogle不了解我們的SimpleType。 因此,記住我們對a的定義,我們實際上擁有uncurry4 SimpleType :: a -> b

所以我有一個Gen a和一個函數Gen a a -> b 我想要Gen b結果。 (請記住,對於我們的情況, a(q,r,s,t)bSimpleType )。 所以我正在尋找一個具有這種類型簽名的函數: Gen a -> (a -> b) -> Gen b 了解這一點 ,並知道GenMonad一個實例,因此我立即認識到liftM是解決我的問題的一元魔術。

Hoogle再次節省了一天。 我知道可能會有一些“提升”組合器來獲得理想的結果,但是老實說,直到我仔細檢查類型簽名之前,我才認為不使用liftM(durrr!)。

這至少是我得到的:

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

順便說一句,我認為這種解決方案在現實世界中不是很有用。 尤其是沒有本地類型類的情況。

檢出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 (liftA4 MyType) (Applicative f) => f Int -> f String -> f Double -> f Char -> f MyType 這與quadraApply的第一個參數不兼容,后者的類型簽名為(a -> a -> a -> a -> b) -> a -> b a-> a-> a-> a- (a -> a -> a -> a -> b) -> a -> b a- (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中是不可能的。

您可以嘗試使用多態性和tyeclasss對此進行歸檔,但是它可能會變得很笨拙,您需要大量擴展才能滿足編譯器的要求。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM