2014-04-02 12 views
9

Tôi có bản ghi với các trường có nhiều loại khác nhau và một hàm có thể áp dụng cho tất cả các loại đó. Là một nhỏ (ngớ ngẩn) ví dụ:Có hệ thống áp dụng một hàm cho tất cả các trường của bản ghi haskell

data Rec = Rec { flnum :: Float, intnum :: Int } deriving (Show) 

Say, tôi muốn xác định một chức năng có thêm hai kỷ lục cho mỗi lĩnh vực:

addR :: Rec -> Rec -> Rec 
addR a b = Rec { flnum = (flnum a) + (flnum b), intnum = (intnum a) + (intnum b) } 

Có một cách để thể hiện điều này mà không lặp lại những hoạt động cho mọi trường (có thể có nhiều trường trong bản ghi)? Trong thực tế, tôi có một bản ghi bao gồm độc quyền các trường Maybe và tôi muốn kết hợp dữ liệu thực với bản ghi chứa giá trị mặc định cho một số trường, sẽ được sử dụng khi dữ liệu thực tế là Nothing.

(Tôi đoán nó phải được thể với mẫu Haskell, nhưng tôi quan tâm nhiều hơn trong việc thực hiện "xách tay".)

Trả lời

5

Bạn có thể sử dụng gzipWithT để làm điều đó.

Tôi không phải là chuyên gia, vì vậy phiên bản của tôi hơi ngớ ngẩn. Chỉ có thể gọi gzipWithT một lần, ví dụ: sử dụng extQextT, nhưng tôi đã không tìm được cách để thực hiện điều đó. Dù sao, đây là phiên bản của tôi:

{-# LANGUAGE DeriveDataTypeable #-} 

import Data.Generics 

data Test = Test { 
    test1 :: Int, 
    test2 :: Float, 
    test3 :: Int, 
    test4 :: String, 
    test5 :: String 
    } 
    deriving (Typeable, Data, Eq, Show) 

t1 :: Test 
t1 = Test 1 1.1 2 "t1" "t11" 

t2 :: Test 
t2 = Test 3 2.2 4 "t2" "t22" 

merge :: Test -> Test -> Test 
merge a b = let b' = gzipWithT mergeFloat a b 
       b'' = gzipWithT mergeInt a b' 
      in gzipWithT mergeString a b'' 

mergeInt :: (Data a, Data b) => a -> b -> b 
mergeInt = mkQ (mkT (id :: Int -> Int)) (\a -> mkT (\b -> a + b :: Int)) 

mergeFloat :: (Data a, Data b) => a -> b -> b 
mergeFloat = mkQ (mkT (id :: Float -> Float)) (\a -> mkT (\b -> a + b :: Float)) 

mergeString :: (Data a, Data b) => a -> b -> b 
mergeString = mkQ (mkT (id :: String -> String)) (\a -> mkT (\b -> a ++ b :: String)) 

main :: IO() 
main = print $ merge t1 t2 

Output:

Test {test1 = 4, test2 = 3.3000002, test3 = 6, test4 = "t1t2", test5 = "t11t22"} 

Mã này là tối nghĩa, nhưng ý tưởng là đơn giản, gzipWithT áp dụng các chức năng quy định chung (mergeInt, mergeString, vv) để ghép của các trường tương ứng.

2

Tôi không nghĩ rằng có cách nào để làm điều này, như để có được các giá trị từ các trường, bạn cần phải xác định tên của chúng hoặc khớp mẫu trên chúng - và tương tự như thiết lập các trường, bạn chỉ định tên của chúng hoặc sử dụng cú pháp hàm dựng thông thường để đặt chúng - nơi thứ tự cú pháp quan trọng.

Có lẽ đơn giản hóa nhẹ sẽ được sử dụng cú pháp constructor thường xuyên và thêm một kết thúc cho sự vận hành

addR' :: Rec -> Rec -> Rec 
addR' a b = Rec (doAdd flnum) (doAdd intnum) 
    where doAdd f = (f a) + (f b) 

doAdd có loại (Num a) => (Rec -> a) -> a.

Ngoài ra, nếu bạn có kế hoạch thực hiện nhiều thao tác trong hồ sơ - ví dụ: subR, gần như giống nhau nhưng trừ - bạn có thể tóm tắt hành vi thành một hàm bằng cách sử dụng RankNTypes.

{-# LANGUAGE RankNTypes #-} 

data Rec = Rec { flnum :: Float, intnum :: Int } deriving (Show) 

opRecFields :: (forall a. (Num a) => a -> a -> a) -> Rec -> Rec -> Rec 
opRecFields op a b = Rec (performOp flnum) (performOp intnum) 
    where performOp f = (f a) `op` (f b) 

addR = opRecFields (+) 

subR = opRecFields (-) 
+0

Tôi hiện đang làm điều 'đóng cửa' này; nó vẫn còn rất nhiều sự trùng lặp. – crosser

5

Tuy nhiên, một cách khác là sử dụng GHC.Generics:

{-# LANGUAGE FlexibleInstances, FlexibleContexts, 
UndecidableInstances, DeriveGeneric, TypeOperators #-} 

import GHC.Generics 


class AddR a where 
    addR :: a -> a -> a 

instance (Generic a, GAddR (Rep a)) => AddR a where 
    addR a b = to (from a `gaddR` from b) 


class GAddR f where 
    gaddR :: f a -> f a -> f a 

instance GAddR a => GAddR (M1 i c a) where 
    M1 a `gaddR` M1 b = M1 (a `gaddR` b) 

instance (GAddR a, GAddR b) => GAddR (a :*: b) where 
    (al :*: bl) `gaddR` (ar :*: br) = gaddR al ar :*: gaddR bl br 

instance Num a => GAddR (K1 i a) where 
    K1 a `gaddR` K1 b = K1 (a + b) 


-- Usage 
data Rec = Rec { flnum :: Float, intnum :: Int } deriving (Show, Generic) 

t1 = Rec 1.0 2 `addR` Rec 3.0 4 
2

với vinyl (một "hồ sơ mở rộng" gói):

import Data.Vinyl 
-- `vinyl` exports `Rec` 

type Nums = Rec Identity [Float, Int] 

tương đương với

data Nums' = Nums' (Identity Float) (Identity Int) 

đó là chính nó tương đương với

data Nums'' = Nums'' Float Int 

sau đó addR chỉ đơn giản là

-- vinyl defines `recAdd` 
addR :: Nums -> Nums -> Nums 
addR = recAdd 

và nếu bạn thêm một lĩnh vực mới

type Nums = Rec Identity [Float, Int, Word] 

bạn không cần phải chạm vào addR.

btw, recAdd rất dễ dàng để xác định chính mình, nếu bạn muốn "nhấc" hoạt động số tùy chỉnh của riêng bạn, nó chỉ là

-- the `RecAll f rs Num` constraint means "each field satisfies `Num`" 
recAdd :: RecAll f rs Num => Rec f rs -> Rec f rs -> Rec f rs 
recAdd RNil RNil = RNil 
recAdd (a :& as) (b :& bs) = (a + b) :& recAdd as bs 

Để thuận tiện, bạn có thể định nghĩa constructor của riêng bạn:

nums :: Float -> Int -> Num 
nums a b = Identity a :& Identity b :& RNil 

và thậm chí là mẫu cho cả giá trị xây dựng và kết hợp:

-- with `-XPatternSynonyms` 
pattern Nums :: Float -> Int -> Num 
pattern Nums a b = Identity a :& Identity b :& RNil 

sử dụng:

main = do 
let r1 = nums 1 2 
let r2 = nums 3 4 
print $ r1 `addR` r2 

let (Nums a1 _) = r1 
print $ a1 

let r3 = i 5 :& i 6 :& i 7 :& z -- inferred 
print $ r1 `addR` (rcast r3) -- drop the last field 

Kể từ r3 được suy ra như

(Num a, Num b, Num c) => Rec Identity [a, b, c] 

bạn có thể (an toàn) bị ném lên trời nó để

rcast r3 :: (Num a, Num b) => Rec Identity [a, b] 

bạn sau đó chuyên nó

rcast r3 :: Nums 

https://hackage.haskell.org/package/vinyl-0.5.2/docs/Data-Vinyl-Class-Method.html#v:recAdd

https://hackage.haskell.org/package/vinyl-0.5.2/docs/Data-Vinyl-Tutorial-Overview.html

Các vấn đề liên quan