考虑以下Haskell代码。
data Keypress = Keypress Int Char
getSeq :: Keypress -> [Char]
getSeq (Keypress i c) = replicate i c
有没有办法以无点形式写getSeq
?
getSeq
的定义与其模式匹配非常相似,似乎可能有某种方式使用currying或monads或其他东西来避免指定i
和{{1}参数。但是,pointfree.io无法解析c
的无点输出,我认为由于模式匹配。
有可能吗?
答案 0 :(得分:16)
通常有用,特别是对于具有多个构造函数的递归数据类型,定义 catamorphism ,这是在为每个可能的构造函数赋予一个函数时折叠数据结构的方法。
例如,对于Bool
,catamorphism是
bool :: a -> a -> Bool -> a
bool x _ False = x
bool _ y True = y
而Either
则是
either :: (a -> c) -> (b -> c) -> Either a b -> c
either f g (Left x) = f x
either f g (Right x) = g x
更高级的catamorphism是列表之一,您之前可能已经看过:foldr
!
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f init [] = init
foldr f init (x:xs) = f x (foldr f init xs)
我们通常不会这样想(或者至少我没有),但是foldr
是一种变形:它处理模式匹配并递归地为你解构列表,只要你为[a]
的两个构造函数中找到的值提供“处理程序”:
[]
的一个案例,根本不需要任何参数:只是b
类型的值(x:xs)
的一个案例。这种情况采用一个表示x
的参数,列表的头部,以及一个表示递归折叠尾部的结果的参数,类型为b
的值。对于只有一个构造函数的类型,catamorphism不那么令人兴奋,但我们可以轻松地为Keypress
类型定义一个:
key :: (Int -> Char -> a) -> Keypress -> a
key f (Keypress x c) = f x c
在某种程度上,catamorphism允许您抽象出函数定义的模式匹配部分,之后您可以使用不再需要直接触及基础数据类型的函数。
一旦定义了这个通常有用的功能,你可以多次使用它来实现你心中所需的任何无点功能。在你的情况下,你可以简单地写
getSeq :: Keypress -> [Char]
getSeq = key replicate
答案 1 :(得分:12)
如上所述,没有。但如果你愿意,你可以解决这个问题:
data Keypress = Keypress
{ count :: Int
, char :: Char }
然后
getSeq p = replicate (count p) (char p)
可以转换为
getSeq = replicate <$> count <*> char
答案 2 :(得分:5)
如果没有一点样板,你就无法做到这一点,但是lens
可以为你生成那个样板,所以这可能就像你要得到的那样接近。
使用makePrisms
中的Control.Lens.TH
将生成Iso
,这实际上是单构造函数数据类型上的第一类模式匹配。方便的是,Iso
是双向的,因此您可以view
它们(以获取值,如模式匹配)和review
它们(将值重新放入,就像使用构造函数一样)正常)。
使用makePrisms
和view
,可以以无点的方式编写getSeq
:
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
data Keypress = Keypress Int Char
makePrisms ''Keypress
getSeq :: Keypress -> [Char]
getSeq = uncurry replicate . view _Keypress
这样更好吗?不知道。您的代码中的模式匹配对我来说很合适,但如果您已经使用lens
,那么此版本可能更适合您。