λ Fun With Functions

Update Everything

Posted on May 24, 2020

The Problem.

In the project I’m working on there is some logic which assigns a UUID to a field in a bunch of values which make up an AST structure representing a user’s code. For testing purposes there are a series of functions which take a value in that structure and wipe out the unique ID so that we can use an equality check against it. Those are manually written functions which we have to update as the structure changes, leading to the odd “Wait, why has that still got a unique ID?” moment when we make changes. I had an inkling that if this was written in Haskell (the code I’m referring to is written in TypeScript) this manually written code, along with the maintenance cost, would be unnecessary.

First Steps.

I’ll be using nix-shell to get myself a REPL to work with the code, using this command:

nix-shell -p "haskellPackages.ghcWithPackages (p: with p; [ghc uniplate pretty-simple])" --command ghci

Turn on an option that we’ll be needing for an easy life:

GHCi> :set -XDeriveDataTypeable

Add an import related to that option, don’t worry about these for now:

GHCi> import Data.Data

Next we’ll use the multi-line input support in GHCi to input this (put :{ on an empty line first and on an empty line after write :} to finish the multi-line editing):

newtype UniqueID = UniqueID { uid :: String }
                   deriving (Eq, Show, Data)

data JSObject = JSObject { objectParts :: [(String, JSValue)] }
                deriving (Eq, Show, Data)

data JSArray = JSArray { arrayParts :: [JSValue] }
               deriving (Eq, Show, Data)

data JSString = JSString { stringValue :: String }
                deriving (Eq, Show, Data)

data JSBool = JSBool { boolValue :: Bool }
              deriving (Eq, Show, Data)

data JSNumber = JSNumber { numberValue :: Double }
                deriving (Eq, Show, Data)

data JSNull = JSNull deriving (Eq, Show, Data)

data JSUndefined = JSUndefined deriving (Eq, Show, Data)

data JSCode = JSCode { code :: String, codeUniqueID :: UniqueID }
              deriving (Eq, Show, Data)

data JSValue = JSValueObject JSObject
             | JSValueArray JSArray
             | JSValueString JSString
             | JSValueBool JSBool
             | JSValueNumber JSNumber
             | JSValueNull JSNull
             | JSValueUndefined JSUndefined
             | JSValueCode JSCode
             deriving (Eq, Show, Data)

What we have above is something that approximates the definition of JSON in most libraries but with the addition of the JSCode type which represents a chunk of arbitrary JavaScript and is where we hold our UniqueID value. As we can have one of those buried 9 layers down, we need to walk the tree to update those wherever we might find them.

Building The Parts.

We’ll start with a function to clear unique ID values, which just throws away the original value and gives us an empty one (use the multi-line support again for this):

clearUniqueID :: UniqueID -> UniqueID
clearUniqueID _ = UniqueID ""

Now build up a value to play with:

GHCi> exampleCode = JSCode "5 + 10" (UniqueID "ABC")
GHCi> exampleArray = JSArray [JSValueCode exampleCode]
GHCi> exampleString = JSString "Good News"
GHCi> exampleFirstValue = ("first", JSValueString exampleString)
GHCi> exampleSecondValue = ("second", JSValueArray exampleArray)
GHCi> exampleObject = JSValueObject (JSObject [exampleFirstValue, exampleSecondValue])

We can use the pretty-simple package to see the structure more clearly:

GHCi> import Text.Pretty.Simple
GHCi> pPrintNoColor exampleObject
JSValueObject 
    ( JSObject 
        { objectParts = 
            [ 
                ( "first" 
                , JSValueString 
                    ( JSString { stringValue = "Good News" } )
                ) 
            , 
                ( "second" 
                , JSValueArray 
                    ( JSArray 
                        { arrayParts = 
                            [ JSValueCode 
                                ( JSCode 
                                    { code = "5 + 10" 
                                    , codeUniqueID = UniqueID { uid = "ABC" }
                                    } 
                                )
                            ]
                        }
                    )
                ) 
            ] 
        }
    )

The Good Stuff.

With the Uniplate library we just need a couple of imports:

GHCi> import Data.Generics.Uniplate.Data
GHCi> import Data.Generics.SYB

Then with the everywhere function we apply our transformation from earlier:

GHCi> updatedExample = everywhere clearUniqueID exampleObject

Lets see the result.

GHCi> pPrintNoColor updatedExample
JSValueObject 
    ( JSObject 
        { objectParts = 
            [ 
                ( "first" 
                , JSValueString 
                    ( JSString { stringValue = "Good News" } )
                ) 
            , 
                ( "second" 
                , JSValueArray 
                    ( JSArray 
                        { arrayParts = 
                            [ JSValueCode 
                                ( JSCode 
                                    { code = "5 + 10" 
                                    , codeUniqueID = UniqueID { uid = "" }
                                    } 
                                )
                            ]
                        }
                    )
                ) 
            ] 
        }
    )

…that’s it! All done, see you next time!

A Deeper Look.

First off, lets have a look at the type of that everywhere function, because that was the most mysterious part.

everywhere :: Biplate b a => (a -> a) -> b -> b

So our clearUniqueID function slotted into the first parameter and then we get a function that transforms from JSValue to JSValue. But this is only if we have an instance of Biplate JSValue UniqueID.

Looking up Biplate we want a Biplate JSValue UniqueID, there’s an instance for (Data a, Data b, Uniplate b) => Biplate a b. So a Data JSValue, a Data UniqueID and a Uniplate UniqueID gives us that Biplate JSValue UniqueID.

Following that chain along to Uniplate, if we look at the instances for it we can see there’s this one: Data a => Uniplate a. So Data a gives us a Uniplate a for every Data a that exists.

Since everything ends up with Data JSValue and Data UniqueID, which GHC has graciously derived automatically for us, we don’t have to write all of that code for walking the various types.

So What Does This Give Us?

The benefits of this are as follows:

  • No manually written pile of code that needs regular maintenance.
  • Reflection or similar runtime introspection is avoided, which might drill into the wrong thing and possibly throw an exception.
  • The types guide the behaviour, so we can guarantee that it’ll be applied to everything it should be and nothing else.

That last point is part of a bigger pattern, not using types as validation but as a building block for behaviour. Having the compiler do work which is repetitive and/or error prone and to the first point which then needs keeping up to date after it is first implemented.

Bonus Round.

There are other wonders to be found in the uniplate library too, like childrenBi which will get all the values matching a particular type from the hierarchy.

GHCi> childrenBi exampleObject :: [UniqueID]
[UniqueID "ABC"]

Maybe Versus Nullable

Posted on November 21, 2017

Select All The Things

Recently I was faced with the task of updating our product to support multiple selection, so that for example a user can move multiple elements together with one mouse drag. As the codebase is written in TypeScript it meant taking this field:

selectedThing: ThingTarget | null

and changing it to this:

selectedThings: Array<ThingTarget>

On the surface this isn’t a huge change, but the code operating on these values has to change quite dramatically because of their different concrete structures. Taking the display of the indicators that show something is selected in the designer as an example:

if (selectedThing == null) {
  return [buildSelectedControl(selectedThing)]
} else {
  return []
}

Now becomes:

return selectedThings.map(buildSelectedControl)

Make All The Changes

These are the simplest examples but fundamentally the logic always needed changing along these lines. As this felt a little unsatisfactory to me I pondered how this would’ve panned out with Haskell (or PureScript/Scala/etc). So we would’ve started with code that looked like:

selectedThing :: Maybe ThingTarget

That would’ve turned into:

selectedThings :: [ThingTarget]

However, in either of these cases the logic is the same and looks like:

fmap buildSelectedControl selectedThings
-- The type of fmap being:
-- fmap :: Functor f => (a -> b) -> f a -> f b

Similarly to check if something has been selected we’d use:

elem thing selectedThings
-- The type of elem being:
-- elem :: (Foldable t, Eq a) => a -> t a -> Bool

When people talk of abstraction it’s often in terms of things close to concrete use cases like a SelectionManager (let’s ignore that nonsense name) class which then doesn’t end up being very re-usable. However with abstractions like Foldable or Functor we have much less specific but just as if not more strict ones which tend more towards general purpose use cases. But also because the language lacks as many special case features (like null and undefined are in JavaScript, even if we don’t think of them traditionally as such) there are fewer hitches to the user of this kind of abstraction.

One Step Further

This extends beyond the functions that can be used to the functions that can be built too:

selectedControls :: (Functor f) = (Editor -> f Thing) -> Editor -> f Control
selectedControls getSelected editor = fmap buildSelectedControl $ getSelected editor

In this case the function is as general as fmap allows, if the accessor function has a type of Editor -> Maybe Thing, then the above function returns a Maybe Control. If no type was supplied, this is what GHC would determined the type of selectedControls would be.

GHCi> data Thing = Thing
GHCi> data Control = Control
GHCi> data Editor = Editor
GHCi> :{
Prelude| buildSelectedControl :: Thing -> Control
Prelude| buildSelectedControl _ = Control
Prelude| :}
GHCi> selectedControls getSelected editor = fmap buildSelectedControl $ getSelected editor
GHCi> :t selectedControls
selectedControls :: Functor f => (t -> f Thing) -> t -> f Control

It turns out this is more flexible that the version further above, as there’s no need (or even way) to constrain the function to the Editor type the compiler makes it polymorphic to any value t.

So What?

I posit that nullable values interfere with code reuse as they’re a disjoint union of the value type and null (undefined as well), but none of the common abstractions can be applied to a disjoint union. Simple types and simple abstractions lead to code reuse and less destructive refactors and if used, more flexible implementations.