剥离newtype构造函数

时间:2015-05-05 18:49:11

标签: haskell constructor strip syntactic-sugar newtype

我经常编写剥离新类型唯一构造函数的函数,例如在下面的函数中返回第一个不是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

4 个答案:

答案 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。如果您不想要预处理步骤,则只需使用alaid使用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