2015-05-05 37 views
7

更多的往往不是我寫的剝離一種新型的唯一的構造函數,如下面的函數返回不是Nothing第一個參數:剝NEWTYPE構造

process (Pick xs) = (\(First x) -> x) . mconcat . map (First . process) $ xs 

我認爲lambda是不必要的冗長。我想寫這樣的事情:

process (Pick xs) = -First . mconcat . map (First . process) $ xs 

Haskell的元編程設施是否允許類似這樣的事情?任何其他解決方案都可以以更簡潔的方式解決此問題,也是值得歡迎的。

UPD。整個代碼已被要求:

data Node where 
    Join :: [Node] -> Node 
    Pick :: [Node] -> Node 
    Given :: Maybe String -> Node 
    Name :: String -> Node 

process :: Node -> Maybe String 
process (Join xs) = liftM os_path_join (mapM process xs) 
process (Pick xs) = getFirst . mconcat . map (First . process) $ xs 
process (Name x) = Just x 
process (Given x) = x 
+0

聽起來像是「脅迫」。 – Zeta

+0

'process'應該是什麼類型?有可能使用'newtype'包來隱藏大部分內容。我所能做的就是'Pick'必須屬於一個遞歸類型,因爲'Pick :: [a] - > PickType'和'process :: PickType - >也許a',而是'First。 process :: PickType - > First a',所以'xs :: [PickType]'? – bheklilr

+0

這只是一個玩具的例子,但我將它添加到OP。 – NioBium

回答

3

澤塔的意見建議,coerce是一個很好的,一般的方式要做到這一點:

process (Pick xs) = coerce . mconcat . map (First . process) $ xs 

另一個約coerce好處是,你可以用它來強制類型構造的「內部」在沒有運行時的成本,這樣的:

example :: [Sum Int] -> [Int] 
example = coerce 

替代,map getFirst,會誘發了map遍歷一個運行時開銷。

此外,在每次製作newtype時間,GHC自動進行適當的Coercible實例,所以你永遠不必擔心與底層機器搞亂(你甚至不需要deriving吧):

newtype Test = Test Char 

example2 :: Maybe Test -> Maybe Char 
example2 = coerce 
4

如果您使用Data.Monoid.First,那麼這裏就是getFirst。許多newtype包裝器使用記錄語法來提供一種簡單的功能來解開新類型。

4

元編程看起來過於複雜。我簡單地使用

unFirst (First x) = x -- define once, use many times 

process (Pick xs) = unFirst . mconcat . map (First . process) $ xs 

通常情況下,函數與新類型一起定義,例如,

newtype First a = First { unFirst :: a } 
+0

謝謝。這樣比較好,但是如果我需要寫很多新類型的話,就像我所要求的那樣,有一些東西還是很不錯的。儘管在這一點上,記錄似乎是我可以做的最好的,但我對它們有個人偏見(在這種形式下,它們看起來多餘且重複;即使你在構造函數名稱的前面添加了「un」而不是「得到」,在我看來,這種情況應該存在統一的處理方式)。 – NioBium

+0

@NioBium現在唯一一種統一的方法是,在你寫的時候,'(\(First x) - > x)'這很麻煩但不那麼長。但我同意採用更直接的統一方式可能會更好。在庫中,你還可以找到'run-'作爲逆操作的通用前綴,至少在monadic newtypes的情況下。 – chi

5

在這種情況下,你可以實際使用的newtypes包更一般地解決這個問題:

process :: Node -> Maybe String 
process (Pick xs) = ala' First foldMap process xs 
process (Join xs) = liftM os_path_join (mapM process xs) 
process (Name x) = Just x 
process (Given x) = x 

你甚至可以有一個更寬泛的版本,需要一個Newtype n (Maybe String)

process' 
    :: (Newtype n (Maybe String), Monoid n) 
    => (Maybe String -> n) -> Node -> Maybe String 
process' wrapper (Pick xs) = ala' wrapper foldMap (process' wrapper) xs 
process' wrapper (Join xs) = liftM os_path_join (mapM (process' wrapper) xs) 
process' wrapper (Name x) = Just x 
process' wrapper (Given x) = x 

Then

> let processFirst = process' First 
> let processLast = process' Last 
> let input = Pick [Given Nothing, Name "bar", Given (Just "foo"), Given Nothing] 
> processFirst input 
Just "bar" 
> ProcessLast input 
Just "foo" 

至於它是如何工作的解釋,ala'功能需要一個NEWTYPE包裝,以確定Newtype實例來使用,在這種情況下,我們想成爲foldMap功能:

foldMap :: (Monoid m, Foldable t) => (a -> m) -> t a -> m 

因爲foldMap f結束是一個普遍的mconcat . map f而不是僅僅是列表的Foldable類型,然後一個函數用作掛鉤到高階函數的「預處理器」被傳遞到ala'foldMap),然後在這種情況下一些Foldable t => t Node來處理。如果您不想要預處理步驟,則只需使用ala,其中id用於其預處理器。使用此功能有時會因爲其複雜的類型而變得困難,但作爲文檔顯示foldMap中的示例通常是一個不錯的選擇。

這樣做的動力是,如果你想寫自己newtype包裝爲Maybe String

newtype FirstAsCaps = FirstAsCaps { getFirstAsCaps :: Maybe String } 

firstAsCaps :: Maybe String -> FirstAsCaps 
firstAsCaps = FirstAsCaps . fmap (fmap toUpper) 

instance Monoid FirstAsCaps where 
    mempty = firstAsCaps Nothing 
    mappend (FirstAsCaps f) (FirstAsCaps g) 
     = FirstAsCaps $ ala First (uncurry . on (<>)) (f, g) 

instance Newtype FirstAsCaps (Maybe String) where 
    pack = firstAsCaps 
    unpack = getFirstAsCaps 

然後

> process' firstAsCaps input 
Just "BAR"