在Haskell中有没有办法表达一个点自由函数来编辑数据类型的属性?

时间:2014-05-29 20:11:04

标签: haskell functional-programming algebraic-data-types

我有类型:

data Cons = Cons {myprop :: String}

稍后我正在映射列表,将属性设置为不同的值:

fmap (\x -> x{myprop = ""}) mylist

是否有点免费方式来表达此匿名函数?

2 个答案:

答案 0 :(得分:7)

这样做没有免费的方法,但您可以使用lens库(附带一大堆好吃的东西):

{-# LANGUAGE TemplateHaskell #-}
import Control.Lens

-- If you put a _ at the beginning of the field name lens can derive the methods for you
data Cons = Cons { _myprop :: String } deriving (Eq, Show)
-- Derives the myprop lens for you (works for all fields in the record)
makeLenses ''Cons
-- You can do this manually as
-- myprop :: Functor f => (String -> f String) -> Cons -> f Cons
-- myprop = lens _myprop (\c newProp -> c { _myprop = newProp })
-- This actually lets you define the lenses for your type without depending on lens
-- which is a bigger deal than you might think.  You can fully support a library and
-- and its paradigms without depending on it at all.

main = do
    let cs = map Cons ["a", "b", "c"]
        -- With the prefix set function
        test1 = fmap (set myprop "") cs
        -- Or with an operator
        test2 = fmap (myprop .~ "") cs
    print cs
    print test1
    print test2

那个"好吃的东西"镜头附带的内容包含

等内容
data Email = Email
    { _emailAccount :: String
    , _emailDomain :: String
    } deriving (Eq, Show)
makeLenses ''Email

data Person = Person
    { _personName :: String
    , _personAge :: Int
    , _personEmail :: Email
    } deriving (Eq, Show)
makeLenses ''Person

testPeople :: [Person]
testPeople = [
    Person "A" 40 (Email "aaa" "gmail.com"),
    Person "B" 45 (Email "bbb" "email.com"),
    Person "C" 50 (Email "ccc" "outlook.com")]

domains :: [Person] -> [String]
domains ps = ps^..traverse.personEmail.emailDomain

statefulFun :: MonadIO m => StateT Person m ()
statefulFun = do
    liftIO $ putStrLn "Changing the name"
    personName .= "a different name"
    liftIO $ putStrLn "The name is changed!"
    personEmail.emailAccount %= map toUpper

moreState :: MonadIO m => StateT Person m ()
moreState = do
    personName .= "Foo"
    zoom personEmail $ do
        current <- get
        liftIO $ putStr "Current email is: "
        liftIO $ print current
        emailAccount .= "foo"
        emailDomain  .= "foo.com"

main :: IO ()
main = do
    finalState <- execStateT (moreState >> statefulFun) (head testPeople)
    print finalState

正如你所看到的,镜头看起来像是倒退(它们实际上并不是,因为它们有一种更通用的类型可以让它们做疯狂的事情)。有很好的遍历复杂数据结构的方法,可选地执行效果,以及大量运算符,用于编写非常强制性的状态代码,看起来像是使用点进行正常的OOP方法访问。一旦你开始理解它们,它可以让你轻松地在大型复杂数据结构上抽象出常见和复杂的模式!

答案 1 :(得分:2)

您可能想要查看镜头....镜头的一个要点是让您更改记录中的单个值,但它们比您的示例更进一步,允许您更改记录中的单个值嵌套在记录中。

例如,请参阅Control.Lens。