16

この例を考えてみましょう(https://codereview.stackexchange.com/questions/23456/crtitique-my-haskell-function-capitalizeから):

import Data.Char

capWord [] = []
capWord (h:t) = toUpper h : map toLower t

capitalize = unwords . map capWord . words

たとえば、「前後の」変換を抽象化するための良い方法はありunwords . f . wordsますか?私が思いついた最高のものは

class Lift a b | a -> b where
  up :: a -> b
  down :: b -> a

instance Lift String [String] where
  up = words
  down = unwords

lifted :: (Lift a b) => (b -> b) -> a -> a
lifted f = down . f . up

capitalize = lifted (map capWord)

しかし、それはあまり柔軟ではなく、、、、および-が必要MultiParamTypeClassesです。FunctionalDependenciesこれは、それがわずかに上を超えていることを示している可能性があります。TypeSynonymInstancesFlexibleInstances

4

5 に答える 5

43

あなたは実際にはfromliftedと同じです:dimapData.Profunctor

onWords = dimap words unwords
capitalize = onWords (map capWord)

それはあなたが考えた一般化の方向ではないかもしれません。しかし、Control.Functorfrom category-extras:の同等の関数のタイプを見てください。

dimap :: Bifunctor f (Dual k) k k => k b a -> k c d -> k (f a c) (f b d)

このバージョンは、aQFunctorとcoの両方であるすべてのものにそれを一般化しますPFunctor。日常のシナリオではそれほど役に立ちませんが、興味深いものです。

于 2013-03-05T22:30:38.723 に答える
11

最良の答えは「いいえ、それを抽象化しても何も買わないから」だと思います。実際、ソリューションの柔軟性ははるかに低くなります。Lift String [String]スコープ内のインスタンスは1つだけであり、文字列を文字列のリストに分割する方法は、単なるものよりwords/unwordsも多くなります(つまり、新しいタイプやさらに多くの難解な拡張機能をミックスに投入し始めることになります) )。シンプルに保ちましょう。オリジナルcapitalizeはそのままで問題ありません。

または、あなたが本当に主張する場合:

lifted :: (a -> b, b -> a) -> (b -> b) -> a -> a
lifted (up, down) f = down . f . up

onWords = lifted (words, unwords)
onLines = lifted (lines, unlines)

capitalize = onWords $ map capWord

型クラスの機械をあまり乱用しないことを除いて、概念的には型クラスと同じものです。

于 2013-03-05T11:15:47.093 に答える
10

これにはレンズを使用できます。レンズは私が思うよりもかなり一般的ですが、そのような双方向機能を持っているものなら何でもレンズにすることができます。

たとえば、とを指定するwordsunwordswordedレンズを作成できます。

worded :: Simple Iso String [String]
worded = iso words unwords

次に、それを使用してレンズ内の関数を適用できます。たとえば、にlifted f xなり(worded %~ f) xます。レンズの唯一の欠点は、ライブラリが非常に複雑であり%~、レンズの核となるアイデアが実際には非常に単純であるにもかかわらず、のような多くのあいまいな演算子が含まれていることです。

編集:これは同型ではありません

unwords . words何人かの人が正しく指摘しているように、単語間の余分なスペースが失われるため、これは恒等関数と同等であると誤って想定していましたが、そうではありません。

代わりに、次のようなはるかに複雑なレンズを使用して、単語間の間隔を維持することができます。それはまだ同型ではないと思いますが、これは少なくともそれを意味しx == (x & worded %~ id)ます、私は願っています。一方で、それは、少なくとも物事を行うための非常に優れた方法ではなく、非常に効率的でもありません。単語のリストではなく、単語自体のインデックス付きレンズの方が優れている可能性がありますが、操作は少なくなります(レンズが関係している場合はわかりにくいと思います)。

import Data.Char (isSpace)
import Control.Lens

worded :: Simple Lens String [String]
worded f s =
    let p = makeParts s
    in fmap (joinParts p) (f (takeParts p))

data Parts = End | Space Char Parts | Word String Parts

makeParts :: String -> Parts
makeParts = startPart
    where
      startPart [] = End
      startPart (c:cs) =
          if isSpace c then Space c (startPart cs) else joinPart (Word . (c:)) cs

      joinPart k [] = k [] End
      joinPart k (c:cs) =
          if isSpace c then k [] (Space c (startPart cs)) else joinPart (k . (c:)) cs

takeParts :: Parts -> [String]
takeParts End = []
takeParts (Space _ t) = takeParts t
takeParts (Word s t) = s : takeParts t

joinParts :: Parts -> [String] -> String
joinParts _ [] = []
joinParts (Word _ End) (ws@(_:_:_)) = unwords ws
joinParts End ws = unwords ws
joinParts (Space c t) ws = c : joinParts t ws
joinParts (Word _ t) (w:ws) = w ++ joinParts t ws
于 2013-03-05T12:11:27.713 に答える
8

DarkOtterが提案したように、Edward Kmettのlensライブラリはあなたをカバーしていますが、アイデンティティではないため、Lens弱すぎIso少し強すぎます。代わりにunwords . words試すことができます。Prism

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

今、あなたは次のように定義することができcapitalizeます

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

しかし、これはあなたのケースではかなり病理学的なデフォルトの振る舞いをしています。String同型写像(内部に複数のスペースが並んでいる文字列)としてマッピングできないsの場合over wordPrism g == id。sには「可能であればオーバー」演算子があるはずですPrismが、私にはわかりません。ただし、次のように定義できます。

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

さて、本当に、あなたが本当に欲しいのはすべての単語を大文字にして間隔を保つことなので、これらの両方はかなり不十分です。これ(words, unwords)は、私が上で強調した同型写像が存在しないため、一般的に弱すぎます。スペースを維持する独自のカスタム機械を作成する必要があります。その後、スペースを確保し、IsoDarkOtterの回答を直接使用できます。

于 2013-03-05T14:52:47.867 に答える
1

それは確かに十分な柔軟性ではありません!行ごとに機能するように関数をどのように持ち上げますか?newtypeそのためのラッパーが必要になります!そのようです

newtype LineByLine = LineByLine { unLineByLine :: String }

instance Lift LineByLine [String] where
    up = lines . unLineByLine
    down = LineByLine . unlines

しかし、今では、行ごとのバージョンよりも単語ごとのバージョンを好む正当な理由はありません。

私はunwords . map f . words、「すべての単語にfを適用し、それらを元に戻す」という慣用句を使用します。これをより頻繁に行う場合は、関数の作成を検討してください。

于 2013-03-05T11:21:04.420 に答える