How to abstract over a “back and forth” transformation?

前端 未结 5 782
栀梦
栀梦 2021-02-19 04:53

Consider this example (from https://codereview.stackexchange.com/questions/23456/crtitique-my-haskell-function-capitalize):

import Data.Char

capWord [] = []
cap         


        
5条回答
  •  北恋
    北恋 (楼主)
    2021-02-19 04:58

    Like DarkOtter suggested, Edward Kmett's lens library has you covered, but Lens is too weak and Iso is slightly too strong since unwords . words isn't an identity. You could try a Prism instead.

    wordPrism :: Prism' String [String]
    wordPrism = prism' unwords $ \s ->
       -- inefficient, but poignant
       if s == (unwords . words) s then Just (words s) else Nothing
    

    Now you can define capitalize as

    capitalize' :: String -> String
    capitalize' = wordPrism %~ map capWord
    -- a.k.a    = over wordPrism (map capWord)
    

    but this has fairly pathological default behavior for your case. For Strings which can't be mapped as isomorphisms (strings with multiple spaces in a row inside of them) over wordPrism g == id. There ought to be an "over if possible" operator for Prisms, but I don't know of one. You could define it though:

    overIfPossible :: Prism s t a b -> (a -> b) -> (s -> Maybe t)
    overIfPossible p f s = if (isn't p s) then Nothing else Just (over p f s)
    
    capitalize :: String -> Maybe String
    capitalize = wordPrism `overIfPossible` map capWord
    

    Now, really, both of these are pretty unsatisfactory since what you really want is to capitalize all words and retain the spacing. For this (words, unwords) is too weak generally due to the non-existence of isomorphism that I've highlighted above. You'd have to write your own custom machinery which maintains spaces after which you'd have an Iso and could use DarkOtter's answer directly.

提交回复
热议问题