Есть ли способ в Haskell выразить функцию без точек для редактирования свойства типа данных?

У меня есть тип:

data Cons = Cons {myprop :: String}

а позже я сопоставляю список, устанавливая для свойства другое значение:

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

Есть ли бесточечный способ выразить эту анонимную функцию?


person Andras Gyomrey    schedule 29.05.2014    source источник


Ответы (2)


Бесточечного способа сделать это не существует, но вместо этого вы можете использовать библиотеку 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

Как вы можете видеть, линзы выглядят как композиция в обратном порядке (на самом деле это не так, потому что у них более общий тип, который позволяет им делать сумасшедшие вещи). Есть хорошие способы обхода сложных структур данных, опциональное выполнение эффектов по мере продвижения и множество операторов для написания очень императивного кода с отслеживанием состояния с тем, что кажется обычным доступом к методу ООП с точкой. Это позволяет вам абстрагировать общие и сложные шаблоны от больших и сложных структур данных с небольшими усилиями, как только вы хотя бы начнете их вникать!

person bheklilr    schedule 29.05.2014

Возможно, вы захотите взглянуть на линзы.... Одна из точек линз - позволить вам изменить одно значение в записи, но они идут намного дальше вашего примера, позволяя вам изменять одно значение в записях, вложенных в записи .

См., например, Control.Lens.

person jamshidh    schedule 29.05.2014