Mitchell Rosen
2017-04-27 02:08:37 UTC
Hello all! I am a (very) beginner Elm programmer, but I know a fair bit of
Haskell. I listened to the most recent Elm Town podcast and was especially
intrigued by the discussion around a novel use of record types to solve a
real-world problem - embedding a nice CSS DSL directly into Elm. Awesome!
I was further intrigued by Evan's suggestion that type classes were in fact
a poor solution to this problem. I'm well aware that type classes are an
overused abstraction in Haskell that can cause lots of pain in unintended
ways, especially when using them for the sole purpose of reusing nice,
short function names.
This is exactly what a CSS DSL must do, however - we'd like to be able to
say both *"overflow: hidden" *and "border-style: hidden" in our DSL by
reaching for the same intuitive name *hidden* and just have everything
magically work. Sounds like exactly the difficult sort of modeling problem
that type classes seem *superficially* suitable, but are often not in
practice.
However, after looking up the venerable elm-css
<https://github.com/rtfeldman/elm-css> library and peeking around its
internals, I'm not sure I agree. The implementation is, in a word,
abstruse. The types that end-users have to see and interact with are highly
non-intuitive. There's a lot of data being slogged around at runtime to
satisfy the type checker. It's simply not a good abstraction. The DSL that
came out on the other end looks great, but at what cost?
For some context, here's how I'd proceed if I had to implement this in
Haskell with type classes.
Say we want to model the key *margin* which can have a value of *auto* or
some integer (simplifying a bit here). I'd write the following type class:
* class Margin a where*
* margin :: a -> Style*
Then, I'd write two instances for the type class - one for my made-up type
*auto* and another for *Int*.
* data Auto = Auto*
* instance Margin Auto where*
* margin Auto = {- implementation... -} instance Margin Int where
margin n = {- implementation... -}*
Here's what the final type of the *margin* combinator looks like:
* margin :: Margin a => a -> Style*
and I could use it like so:
* margin Auto*
* margin 5*
Now, I'm not necessarily advocating this approach (DSL design is hard), but
there it is, for reference. I happen to share a lot of the sentiments
expressed in the podcast about type classes, but in this specific case,
they actually don't seem too bad. Compare the above with the machinery in
for margin that exists in elm-css:
*type alias LengthOrAuto compatible =*
* { compatible | value : String, lengthOrAuto : Compatible }*
* margin : LengthOrAuto compatible -> Style*
* auto :*
* { lengthOrAuto : Compatible*
* , overflow : Compatible*
* , textRendering : Compatible*
* , flexBasis : Compatible*
* , lengthOrNumberOrAutoOrNoneOrContent : Compatible*
* , alignItemsOrAuto : Compatible*
* , justifyContentOrAuto : Compatible*
* , cursor : Compatible*
* , value : String*
* , lengthOrAutoOrCoverOrContain : Compatible*
* , intOrAuto : Compatible*
* }
*
Compatible, what? Some value field is a String? Keep in mind these type are
all visible to the user - necessarily so - so they can figure out how to
fit the pieces together. However, I don't think it's unreasonable to say
that the only way (or at least the *main *way) to use this library is to
shut your brain off and trust that if you get any compiler error, it's
because you did some illegal CSS thing. It's simultaneously exposes ugly
innards (type aliases) and yet resists when you try to grok what's going on
(Compatible is not exported, though for good reason).
It concede it's possible I have overlooked some part of the library where
the simple type class approach breaks down. But when attacked top-down (I
want the DSL to come out looking like *this *so I need to use *these
language features*), I'm not seeing why one would prefer this row-types
approach over type classes if both happened to exist in Elm.
Thanks for reading, and if this is the umpteenth post about type classes, I
apologize. I figured at least the concept of type classes still relevant in
this community, given they were briefly discussed on Elm Town just today.
I'd love to hear any seasoned Elm-ers thoughts about row types as an
abstraction mechanism, type classes or lack thereof, elm-css, or anything
else that came to mind while reading over this post.
Mitchell
Haskell. I listened to the most recent Elm Town podcast and was especially
intrigued by the discussion around a novel use of record types to solve a
real-world problem - embedding a nice CSS DSL directly into Elm. Awesome!
I was further intrigued by Evan's suggestion that type classes were in fact
a poor solution to this problem. I'm well aware that type classes are an
overused abstraction in Haskell that can cause lots of pain in unintended
ways, especially when using them for the sole purpose of reusing nice,
short function names.
This is exactly what a CSS DSL must do, however - we'd like to be able to
say both *"overflow: hidden" *and "border-style: hidden" in our DSL by
reaching for the same intuitive name *hidden* and just have everything
magically work. Sounds like exactly the difficult sort of modeling problem
that type classes seem *superficially* suitable, but are often not in
practice.
However, after looking up the venerable elm-css
<https://github.com/rtfeldman/elm-css> library and peeking around its
internals, I'm not sure I agree. The implementation is, in a word,
abstruse. The types that end-users have to see and interact with are highly
non-intuitive. There's a lot of data being slogged around at runtime to
satisfy the type checker. It's simply not a good abstraction. The DSL that
came out on the other end looks great, but at what cost?
For some context, here's how I'd proceed if I had to implement this in
Haskell with type classes.
Say we want to model the key *margin* which can have a value of *auto* or
some integer (simplifying a bit here). I'd write the following type class:
* class Margin a where*
* margin :: a -> Style*
Then, I'd write two instances for the type class - one for my made-up type
*auto* and another for *Int*.
* data Auto = Auto*
* instance Margin Auto where*
* margin Auto = {- implementation... -} instance Margin Int where
margin n = {- implementation... -}*
Here's what the final type of the *margin* combinator looks like:
* margin :: Margin a => a -> Style*
and I could use it like so:
* margin Auto*
* margin 5*
Now, I'm not necessarily advocating this approach (DSL design is hard), but
there it is, for reference. I happen to share a lot of the sentiments
expressed in the podcast about type classes, but in this specific case,
they actually don't seem too bad. Compare the above with the machinery in
for margin that exists in elm-css:
*type alias LengthOrAuto compatible =*
* { compatible | value : String, lengthOrAuto : Compatible }*
* margin : LengthOrAuto compatible -> Style*
* auto :*
* { lengthOrAuto : Compatible*
* , overflow : Compatible*
* , textRendering : Compatible*
* , flexBasis : Compatible*
* , lengthOrNumberOrAutoOrNoneOrContent : Compatible*
* , alignItemsOrAuto : Compatible*
* , justifyContentOrAuto : Compatible*
* , cursor : Compatible*
* , value : String*
* , lengthOrAutoOrCoverOrContain : Compatible*
* , intOrAuto : Compatible*
* }
*
Compatible, what? Some value field is a String? Keep in mind these type are
all visible to the user - necessarily so - so they can figure out how to
fit the pieces together. However, I don't think it's unreasonable to say
that the only way (or at least the *main *way) to use this library is to
shut your brain off and trust that if you get any compiler error, it's
because you did some illegal CSS thing. It's simultaneously exposes ugly
innards (type aliases) and yet resists when you try to grok what's going on
(Compatible is not exported, though for good reason).
It concede it's possible I have overlooked some part of the library where
the simple type class approach breaks down. But when attacked top-down (I
want the DSL to come out looking like *this *so I need to use *these
language features*), I'm not seeing why one would prefer this row-types
approach over type classes if both happened to exist in Elm.
Thanks for reading, and if this is the umpteenth post about type classes, I
apologize. I figured at least the concept of type classes still relevant in
this community, given they were briefly discussed on Elm Town just today.
I'd love to hear any seasoned Elm-ers thoughts about row types as an
abstraction mechanism, type classes or lack thereof, elm-css, or anything
else that came to mind while reading over this post.
Mitchell
--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.