我有这样的功能:
test :: [Block] -> [Block]
test ((Para foobar):rest) = [Para [(foobar !! 0)]] ++ rest
test ((Plain foobar):rest) = [Plain [(foobar !! 0)]] ++ rest
Block
是包含Para
,Plain
和其他内容的数据类型。
函数的作用并不是特别重要,但我注意到[Para [(foobar !! 0)]] ++ rest
和Para
函数的主体(Plain
)是相同的,除了使用的构造函数是类型foobar
。
问题:有两种情况合并写一下这个函数的简洁吗? 像
这样的东西test :: [Block] -> [Block]
test ((ParaOrPlain foobar):rest) = [ParaOrPlain [(foobar !! 0)]] ++ rest
第一个ParaOrPlain
与Para foobar
或Plain foobar
匹配,第二个ParaOrPlain
分别为Para
或Plain
。
请注意,Block
也可以是(例如)BulletList
或OrderedList
,我不想对其进行操作。 (编辑:test x = x
以获取其他类型。)
关键是我不想复制函数体两次,因为它们是相同的(除非调用Para
或Plain
)。
我觉得我可以使用Either
,或者我自己的数据类型,但我不知道该怎么做。
修改 为了澄清,我知道我的函数体很麻烦(我是Haskell的新手),我感谢各种回答者的简化。
然而,问题的核心是,我希望避免复制Para
和Plain
行。像(用我的制作语言......)
# assume we have `test (x:rest)` where `x` is an arbirtrary type
if (class(x) == 'Para' or class(x) == 'Plain') {
conFun = Plain
if (class(x) == 'Para')
conFun = Para
return [conFun ...]
}
# else do nothing (ID function, test x = x)
return x:rest
即,我想知道在Haskell中是否有可能以这种方式根据输入参数的类型分配构造函数。希望澄清这个问题。
答案 0 :(得分:3)
一种方法是使用(多态)辅助函数,例如:
helper ctor xs rest = ctor [ head xs ] : rest
test :: [Block] -> [Block]
test ((Para xs):rest) = helper Para xs rest
test ((Plain xs):rest) = helper Plain xs rest
test bs = bs
请注意,Para
和Plain
只是[whatever] -> Block
类型的函数。
答案 1 :(得分:3)
假设表格中的数据类型声明:
data Block
= Para { field :: [Something] }
| Plain { field :: [Something] }
...
您可以简单地使用通用记录语法:
test :: [Block] -> [Block]
test (x:rest) = x { field = [(field x) !! 0] } : rest
答案 2 :(得分:1)
首先请注意,正如user5402已经写过的那样,您应该将[Para [(foobar !! 0)]] ++ rest
替换为Para [foobar !! 0] : rest
,这看起来已经不错了。接下来请注意,基本上,你所做的只是修改列表的头部,所以作为一个有意义的帮助我会使用
modifyHead :: (a->a) -> [a] -> [a]
modifyHead f (x:xs) = f x : xs
modifyHead _ [] = []
test = modifyHead m
where m (Para foobar) = Para [head foobar] -- `head` is bad, but still better than `(!!0)`!
m (Plain foobar) = Plain [head foobar]
答案 3 :(得分:1)
这与我在不使用Template Haskell或Data.Data时可以得到的一样好: - )
首先,我们需要启用一些扩展并修复Block
数据类型以获得具体性(如果我的简化假设是错误的,请告诉我并且我将看到可以挽救的内容!)
{-# LANGUAGE GADTs, LambdaCase #-}
data Block = Para [Int]
| Plain [String]
| Other Bool Block
| YetAnother deriving (Show,Eq)
这里重要的一点是Para
和Plain
是一元构造函数,但数据类型本身可以包含具有不同数量参数的构造函数。
正如@leftaroundabout和@ user5402解释的那样,我们可以将修改单个Block
并将该修改仅应用于列表的第一个元素的问题分开,因此我将专注于重写
modifyBaseline :: Block -> Block
modifyBaseline (Para xs) = Para [xs !! 0]
modifyBaseline (Plain xs) = Plain [xs !! 0]
modifyBaseline rest = rest
接下来,我们需要能够将一元构造函数作为值进行讨论。这里有三件重要的事情:
我们很好地将其打包成自定义类型(t
是构造函数所属的类型,而a
就是其中的内容):
data UnaryConstructor t a = UnaryConstructor {
destruct:: t -> Maybe a
construct :: a -> t
}
现在我们可以定义
para :: UnaryConstructor Block [Int]
para = UnaryConstructor (\case { Para ns -> Just ns ; _ -> Nothing }) Para
plain :: UnaryConstructor Block [Int]
plain = UnaryConstructor (\case { Plain ss -> Just ss ; _ -> Nothing }) Plain
(如果你写LambdaCase
,你可以摆脱(\xs -> case xs of { Para ns -> Just ns; _ -> Nothing})
扩展名,但这种方式很好而且紧凑!)
接下来,我们需要打包'一元构造函数和应用于其中包含的值的函数:
data UnaryConstructorModifier t where
UnaryConstructorModifier :: UnaryConstructor t a -> (a -> a) -> UnaryConstructorModifier t
这样我们就可以写
了modifyHelper :: [UnaryConstructorModifier t] -> t -> t
modifyHelper [] t = t
modifyHelper ((UnaryConstructorModifier uc f):ucms) t
| Just x <- destruct uc t = construct uc (f x)
| otherwise = modifyHelper ucms t
最后(lambda可能是(\xs -> [xs !! 0])
或(\xs -> [head xs])
尝试):
modify :: Block -> Block
modify = modifyHelper [UnaryConstructorModifier para (\(x:_) -> [x]),
UnaryConstructorModifier plain (\(x:_) -> [x])]
如果我们现在用
测试它ghci> let blocks = [Para [1,2,3,4], Plain ["a","b"], Other True (Para [42,43]), YetAnother ]
ghci> map modify blocks == map modifyBaseline blocks
我们得到True
- 万岁!
重复位现在位于para
和plain
的定义中,您必须将构造函数的名称写入三次,但如果不使用Template Haskell,则无法解决这个问题。或Data.Data(我可以看到)。
进一步改进的选择是为不同arity的构造函数做一些事情,并在a -> Maybe a
中放置UnaryConstructorModifier
类型的函数来处理(\(x:_) -> [x]
的偏倚但是我认为这很好地回答了你的问题。
我希望你能理解这一点,因为我已经掩盖了一些细节,包括UnaryConstructorModifier
定义中的内容以及modifyHelper
中使用的模式保护{1}} - 所以问你是否需要澄清!
答案 4 :(得分:1)
您最接近原始想法的可能是“通用Para
- 或 - Plain
- 修改”功能。
{-# LANGUAGE RankNTypes #-}
modifyBlock :: (forall a . [a]->[a]) -> Block -> Block
modifyBlock f (Plain foobar) = Plain $ f foobar
modifyBlock f (Para foobar) = Plain $ f foobar
观察f
每次使用不同的类型!
然后,你可以写
test (b:bs) = modifyBlock (\(h:_) -> [h]) b : bs
答案 5 :(得分:1)
这可以通过ViewPatterns
注意:这里实际上并不需要查看模式,只是让它看起来更漂亮imho
注意':这假设在两种情况下块内的列表都是相同的类型
{-# LANGUAGE ViewPatterns #-}
paraOrPlain :: Block a- > Maybe (a -> Block a, a)
paraOrPlain (Plain xs) = Just (Plain,xs)
paraOrPlain (Para xs) = Just (Para,xs)
paraOrPlain _ = Nothing
-- or even better
para (Para xs) = Just (Para,xs)
para _ = Nothing
plain (Plain xs) = Just (Plain,xs)
plain _ = Nothing
paraOrPlain' b = para b <|> plain b -- requires Control.Applicative
test ((paraOrPlain -> Just (con,xs)) : rest) = con (take 1 xs) : rest
当然,你仍然需要在某个地方进行模式匹配 - 如果没有TH或Generic,你就无法做到这一点 - 但是你可以用一种有助于重用的方式来做到这一点。