abesto
abesto

Reputation: 2351

Any standard abstraction like this? (aka. Guess The Structure)

I came up with the following structure while refactoring a hobby project. I'm wondering if it matches, or can be tweaked to match, some existing abstraction / structure. I started reading up on Applicative, Functor and Arrows, but couldn't make the connection, and I don't have more ideas. So, the minimal useful example:

-- trivial struct for this minimal example
data T = T { n :: Int, s :: String }

-- for any type `a`, there's exactly one way to update a `T`
class Gen a where
  gen :: a -> T -> T

-- helpers to compose `a`-s into a (T -> T)
(.>) :: (Gen a) => (T -> T) -> a -> (T -> T)
l .> x = l . (gen x)

(<.>) :: (Gen a, Gen b) => a -> b -> T -> T
l <.> r = (gen l) . (gen r)

-- For this example, let's say an Int is used to update a T by adding to its `n`
instance Gen Int where
  gen x t = t { n = (n t) + x }

-- and a Char is prepended to its `s`
instance Gen Char where
  gen c t = t { s = c : (s t) }

-- I can now express things like this easily
appendFooAndAdd3 = 'F' <.> 'o' .> 'o' .> (3::Int)

The actual code, in case more context is useful: https://github.com/abesto/hsircd/blob/cc8e9e33617f61ef9417b9476856a9fdc6bc4948/src/Server.hs#L81-L135

If you find some structure that can be used here, I'd be extra grateful if you could explain how you came to the conclusion. I imagine I could learn a lot from it.

Edit: to clarify what I'm asking: can this be implemented by using some existing abstraction? Applicative and Functor are examples of what I mean, but they don't seem to match this case. If yes, how did you come to the structure?

Upvotes: 1

Views: 110

Answers (1)

Daniel Wagner
Daniel Wagner

Reputation: 153162

So, I had a look through the documentation for lens to figure out how to do this. It seems this is the idiomatic way:

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

data T = T
    { _n :: Int
    , _s :: String
    } deriving (Eq, Ord, Read, Show)

makeLenses ''T

appendFooAndAdd3 = (s <>~ "Foo") . (n +~ 3)

You'll notice that things are not quite as implicit in your approach: in s <>~ "Foo", one must name the field s and the operation <> one wants to perform, and similarly in n +~ 3. Nevertheless this may be seen as a strength, as it is fairly rare that there is just one way to modify a T.

In this case, I think the Pythonism "explicit is better than implicit" applies.

Upvotes: 4

Related Questions