2015-05-05 15 views
7

Thường xuyên hơn không Tôi viết thư này chức năng mà đang tước các nhà xây dựng duy nhất của một loại mới, chẳng hạn như trong các chức năng sau đây để trả lại số đầu tiên mà không phải là không có gì:Tước constructor Newtype

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

tôi nghĩ rằng lambda là không cần thiết tiết. Tôi muốn viết một cái gì đó như thế này:

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

Các cơ sở lập trình meta của Haskell có cho phép bất kỳ điều gì tương tự không? Bất kỳ giải pháp nào khác để giải quyết vấn đề này một cách ngắn gọn hơn cũng được hoan nghênh.

UPD. Toàn bộ mã đã được yêu cầu:

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

Âm thanh như 'coerce'. – Zeta

+0

Loại 'process' được cho là gì? Có thể sử dụng gói 'newtype' để ẩn hầu hết những điều này. Tất cả tôi có thể làm cho nó là 'Pick' phải thuộc về một loại đệ quy, kể từ' Chọn :: [a] -> PickType', và 'process :: PickType -> Có thể a', nhưng' Đầu tiên. process :: PickType -> First a', vì vậy 'xs :: [PickType]'? – bheklilr

+0

Nó chỉ là một ví dụ đồ chơi, nhưng tôi sẽ thêm nó vào OP. – NioBium

Trả lời

3

Như Zeta gợi ý trong các ý kiến, coerce là một cách nói chung đẹp để làm điều này:

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

Loại khác điều tốt đẹp về coerce là bạn có thể sử dụng nó để ép buộc "bên trong" của một nhà xây dựng kiểu hoàn toàn miễn phí chạy, như thế này:

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

Việc thay thế, map getFirst, sẽ tạo ra một chi phí thời gian chạy cho map traversal.

Ngoài ra, mỗi khi bạn thực hiện một newtype, GHC tự động làm cho Coercible dụ thích hợp, do đó bạn không bao giờ phải lo lắng về việc can thiệp vào máy móc thiết bị cơ bản (bạn thậm chí không cần deriving cho nó):

newtype Test = Test Char 

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

Nếu bạn đang sử dụng Data.Monoid.First, thì đây chỉ là getFirst. Nhiều trình bao bọc newtype sử dụng cú pháp ghi để cung cấp một hàm dễ dàng để gỡ bỏ kiểu mới.

4

Lập trình meta trông quá phức tạp cho việc này. Tôi chỉ cần sử dụng

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

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

Thường là trường hợp hàm được xác định cùng với loại mới, ví dụ:

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

Cảm ơn. Điều này là tốt hơn, nhưng nếu tôi cần phải viết rất nhiều loại mới, nó vẫn sẽ được tốt đẹp để có một cái gì đó, giống như những gì tôi yêu cầu. Mặc dù vào thời điểm này có vẻ như các bản ghi là tốt nhất tôi có thể làm, tôi có một định kiến ​​cá nhân chống lại chúng (trong hình thức này chúng cũng có vẻ dư thừa và lặp đi lặp lại, thậm chí bạn đã phạm sai lầm của tiền tố tên Constructor và "un", trái ngược với "nhận được", và theo ý kiến ​​của tôi nên có một điều trị thống nhất của trường hợp này). – NioBium

+0

@NioBium Cách duy nhất thống nhất hiện tại là, khi bạn viết, '(\ (First x) -> x)' đó là cồng kềnh nhưng không phải là dài. Tuy nhiên, tôi đồng ý rằng có một cách thống nhất trực tiếp hơn có thể được tốt đẹp. Trong các thư viện, bạn cũng tìm thấy 'run-' như một tiền tố chung cho hoạt động nghịch đảo, ít nhất là trong bối cảnh của các kiểu mới đơn thuần. – chi

5

Trong trường hợp này, bạn thực sự có thể sử dụng gói newtypes để giải quyết vấn đề này quát hơn:

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 

Bạn thậm chí có thể có một phiên bản chung chung hơn mà phải mất một Newtype n (Maybe String) như

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 

Sau đó,

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

Như một lời giải thích cho cách làm này, các ala' chức năng phải mất một wrapper Newtype để xác định trường hợp của Newtype sử dụng, một chức năng mà trong trường hợp này, chúng tôi muốn trở thành foldMap:

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

từ foldMap f kết thúc là tổng quát mconcat . map f trên Foldable loại thay vì chỉ danh sách, sau đó một hàm để sử dụng làm "bộ tiền xử lý" để gắn vào hàm bậc cao hơn được chuyển đến ala' (foldMap), trong trường hợp này, một số Foldable t => t Node để xử lý. Nếu bạn không muốn bước tiền xử lý bạn chỉ sử dụng ala, sử dụng id cho bộ tiền xử lý của nó. Sử dụng chức năng này đôi khi có thể khó khăn do loại phức tạp của nó, nhưng như các ví dụ trong tài liệu hiển thị foldMap thường là một lựa chọn tốt.

Sức mạnh của việc này là nếu bạn muốn viết newtype wrapper của riêng bạn cho 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 

Sau đó

> process' firstAsCaps input 
Just "BAR" 
Các vấn đề liên quan