mdgriffith / style-elements-design-discussion

BSD 3-Clause "New" or "Revised" License
8 stars 0 forks source link

Handling of Infix Operators #17

Closed mdgriffith closed 7 years ago

mdgriffith commented 7 years ago

To start this off I think Elm's avoidance of infix operators is generally a really good design decision.

That being said, I'm running into an interesting issue. In style-elements we want to have grouped properties, which was initially proposed using the pipeline operator. This looks something like this:

--...
     class LargerStyle
            [ Font.named roboto
                |> Font.size 18
                |> Font.letterSpacing 20
                |> Font.light
                |> Font.align center
                |> Font.uppercase
                |> Font.color Color.blue
            , box
                |> width (px 100)
                |> height (px 100)
                |> padding (all 5)
            ]

The problem I ran into is that for this to work out in types, then each category is just a Property and each modifier is a Property -> Property. This actually means that you could have something like this:

     class LargerStyle
            [ Font.named roboto
                |> padding (all 5)
            , box
                |> Font.size 20
            ]

This is weird. It means the groups aren't enforced in the type level. Also, with the above type signature you could just have the empty box property which doesn't do anything. Again, sorta weird.

I can make the above code issue a runtime error if you try to mix up the functions with the starting "atoms", but obviously I'd much rather have a compiler error.

This is compounded by the fact that the Property type is actually Property class variation animation, because the library needs to keep track of what types are being used for classes, variations and animations. So Property -> Property shows up as Property class variation animation -> Property class variation animation, which is kinda weird for a function that does something like "set border color".

Alternative

Here's an example of what I'd like a modifier function to actually look like:

Border.color : Color -> Border -> Border

Given the above code, the "atom" can't just be of type Border though, which it would need to be in order for pipelines to work out. The reason being is that a style class is gotta be a list of one type, so what happens when we have a Font with our border?

So, the "atom" needs to take a Border and transform it into a Property class variation animation.
Actually the atom needs to take a Border -> Border and transform it into a property for this to work.

Here's what the final code turns out to be to make the groups actually enforced:

 class LargerStyle
            [ Font.named roboto
                <| Font.size 18
                << Font.letterSpacing 20
                << Font.light
                << Font.align center
                << Font.uppercase
                << Font.color Color.blue
            , box
                <| width (px 100)
               << height (px 100)
               << padding (all 5)
            ]

We're composing a huge function and then feeding it into the atom, which seeds it with an empty Border or Box or Font, and then transforms that into a Property. Conceptually I don't think that's too crazy, but the reason I'm posting this issue is for feedback on if I'm nuts.

An immediate issue of the above syntax is that elm-format will reformat it to look super funky because <| is really meant for inline statements, not pipelines.

Aesthetically, it's also really busy. The vertical line in |> created a nice continuity for each group.

So, here's the solution I'm looking at. Essentially we're defining two "new" infix operators that do nearly the exact thing as the above.

     class LargerStyle
            [ Font.named roboto
                |^ Font.size 18
                |- Font.letterSpacing 20
                |- Font.light
                |- Font.align center
                |- Font.uppercase
                |- Font.color Color.blue
            , box
                |^ width (px 100)
                |- height (px 100)
                |- padding (all 5)
            ]

The infix operators would be defined in the library as:

{-| This is not a fancy operator.

This is a synonym for `<|`.  If `<|` was used instead, `elm-format` would make your styles look weird.

-}
(|^) : (a -> b) -> a -> b
(|^) =
    (<|)
infixr 0 |^

{-| This is not a fancy operator.

This is just a synonym for `<<`, but with an adjusted infix priority so that it plays nicely with `|^`.

I highly recommending only using this when dealing with this library, it's not meant as a general operator.
-}
(|-) : (b -> c) -> (a -> b) -> (a -> c)
(|-) =
    (<<)
infixl 1 |-
Dremora commented 7 years ago

I don't find this solution particularly crazy. This could potentially also lead to making the actual types of atoms and properties more opaque (that is, not exposing the fact that Properties are actually functions), but I couldn't come up with a way to make this work without typeclasses.

The type signature of |- could also be more specific to prevent it from being used as a generic << replacement:

(|-) : (a -> a) -> (a -> a) -> (a -> a)
opsb commented 7 years ago

As a perhaps simpler alternative, how about using a nested list structure?

class LargerStyle
    [ font 
        [ Font.named roboto
        , Font.size 18
        , Font.letterSpacing 20
        , Font.light 
        , Font.align center
        , Font.uppercase
        , Font.color Color.blue
        ]
    , box
        [ width (px 100)
        , height (px 100)
        , padding (all 5)
        ]
    ]

where

font : List FontProperty -> Property

box : List BoxProperty -> Property
mdgriffith commented 7 years ago

That makes a lot of sense!

mdgriffith commented 7 years ago

This was resolved using obsp's suggestion! No crazy infix operators.