我经常编写剥离新类型唯一构造函数的函数,例如在下面的函数中返回第一个不是Nothing的参数:
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 :(得分: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
然后
> 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"
然后
[raul.tabacu@fep-62-1 Licenta]$ ls -la
total 513
drwxr-xr-x 9 raul.tabacu studcs 573 2015-05-05 23:05 .
drwx------ 27 raul.tabacu studcs 12288 2015-05-05 22:42 ..
-rwxr-xr-x 1 raul.tabacu studcs 93 2014-11-08 19:26 compareResults.sh
-rwxr-x--- 1 raul.tabacu studcs 165525 2015-05-05 22:37 crosspn7.dat
-rwxrwx--- 1 raul.tabacu studcs 165492 2014-10-25 21:45 jkl
-rw-r--r-- 1 raul.tabacu studcs 445 2015-02-05 08:15 Makefile
drwxr-xr-x 4 raul.tabacu studcs 102 2014-11-08 19:13 rezultate
drwxrwx--- 2 raul.tabacu studcs 565 2015-05-05 22:56 runConfigs
-rwxr-xr-x 1 raul.tabacu studcs 287 2014-11-04 22:12 runexp.sh
drwxrwx--- 40 raul.tabacu studcs 12288 2015-04-19 15:42 Runner
....
-rw-r--r-- 1 raul.tabacu studcs 0 2015-05-05 22:55 tutu.dat
答案 1 :(得分:4)
如果你正在使用Data.Monoid.First
,那么这只是getFirst
。许多newtype包装器使用记录语法来提供一个简单的函数来打开newtype。
答案 2 :(得分:4)
Meta编程看起来过于复杂。我只是使用
unFirst (First x) = x -- define once, use many times
process (Pick xs) = unFirst . mconcat . map (First . process) $ xs
通常情况下,函数与newtype一起定义,例如
newtype First a = First { unFirst :: a }
答案 3 :(得分:3)
正如Zeta在评论中所说,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