Default Values for Record Properties

675 views
Skip to first unread message

Joe Fiorini

unread,
Dec 12, 2014, 2:32:31 PM12/12/14
to elm-d...@googlegroups.com
I know that records are Elm's replacement for Haskell's type classes, and according to the blog records can achieve almost everything type classes can. Haskell's type classes let you add default implementations that all instances will automatically pick up. Based on a quick search in this group, it appears this feature is missing from Elm. There are some interesting ideas proposed (https://groups.google.com/forum/#!searchin/elm-discuss/records$20optional/elm-discuss/gvmNskIhWKw/QOYlLr9lbUEJ); has any work on this been done? If not, has anyone found a way to use records in such a way that you don't have to provide a value for every property? An obvious way would be to make optional properties Maybe or have an Optional type, but that would still require setting every property in order to build a type, which is what I'm trying to avoid.

Thanks!

Jeff Smits

unread,
Dec 12, 2014, 3:19:17 PM12/12/14
to elm-discuss

I always figured we'd use plain old functions that take some of the fields and add the others from there.

On 12 Dec 2014 20:32, "Joe Fiorini" <j...@joefiorini.com> wrote:
I know that records are Elm's replacement for Haskell's type classes, and according to the blog records can achieve almost everything type classes can. Haskell's type classes let you add default implementations that all instances will automatically pick up. Based on a quick search in this group, it appears this feature is missing from Elm. There are some interesting ideas proposed (https://groups.google.com/forum/#!searchin/elm-discuss/records$20optional/elm-discuss/gvmNskIhWKw/QOYlLr9lbUEJ); has any work on this been done? If not, has anyone found a way to use records in such a way that you don't have to provide a value for every property? An obvious way would be to make optional properties Maybe or have an Optional type, but that would still require setting every property in order to build a type, which is what I'm trying to avoid.

Thanks!

--
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.

Joe Fiorini

unread,
Dec 12, 2014, 3:45:57 PM12/12/14
to elm-d...@googlegroups.com
That is the approach I was thinking too. Unfortunately, I think it would require a separate function for every combination of default fields, based on the way record types work. For example, say I have a record type:

type alias RenderOptions =
  { container: Box
  , title: String
  , background: String
  , attributes: List Html.Attribute
  }

If I wanted each of these to have defaults, following this approach I'd need 24 functions to accept the correct combination of properties. A few examples:

mkOptionsWithContainer : {container} -> RenderOptions
mkOptionsWithTitle : {title} -> RenderOptions
mkOptionsWithContainerAndTitle : {container,title} -> RenderOptions

Another way I thought to do it was just not define a type signature so I can say:

mkOptions o = { defaults | container <- if o.container then o.container else defaults.container
                                        , title <- if o.title then o.title else defaults.title }

Obviously this wouldn't work because a) o.container doesn't return a Boolean and b) by calling both .container & .title on o the compiler is going to infer a type {container,title} on my object, and therefore blow up when I pass an object that only has {container}.

It seems like elegantly allowing default values for records is just not possible right now. Experience has taught me that when what I want to do seems impossible in my chosen stack, I'm probably doing the wrong thing. Am I overlooking a simpler way to accomplish this with other features of the Elm language?

Evan Czaplicki

unread,
Dec 12, 2014, 3:55:57 PM12/12/14
to elm-d...@googlegroups.com
Why not create a default version?

type alias RenderOptions =
  { container: Box
  , title: String
  , background: String
  , attributes: List Html.Attribute
  }

-- I don't know what a "Box" is so I used a ? that you can fill in
defaultRenderOptions =
    { container = ?
    , title = "Dummy Title"
    , background = ?
    , attributes = []
    }

myOptions =
    { defualtRenderOptions |
        title <- "My Thing"
    }

I'm not sure what you mean about not being able to write down a type. For anything you write in Elm, you can write a type signature, so we can help out writing a type for a specific function if you are not sure.

I do not really see how type classes are relevant to providing default values. I have seen a Def type class in some Haskell libraries, but it reliably makes my life worse because I do not know how to change the defaults. Sometimes you end up creating newtypes... It's pretty crazy, and I'm glad people cannot create that experience for others in Elm.

Jeff Smits

unread,
Dec 12, 2014, 4:06:54 PM12/12/14
to elm-discuss

Evan, the idea is that typeclasses in Haskell are related to explicit record passing in Elm. Typeclasses in Haskell allow default implementation. Joe is looking for something of that power for Elm records. This isn't about typeclass functionality per se, it's about wanting something Haskell typeclasses happen to have.

Evan Czaplicki

unread,
Dec 12, 2014, 4:21:14 PM12/12/14
to elm-d...@googlegroups.com
Default implementation? Can you show me an example?

Joe Fiorini

unread,
Dec 12, 2014, 4:52:02 PM12/12/14
to elm-d...@googlegroups.com
Here's an example that I'm pretty sure is referenced in Learn You a Haskell, taken from A Gentle Introduction to Haskell.

class  Eq a  where
  (==), (/=)            :: a -> a -> Bool
  x /= y                =  not (x == y)

So the Eq class defaults the definition of "/=" to the inverse of "==". Of course, instances can override if they want. It would be like if in Elm I could say:

type alias Eq a = { a | (==) : a -> a -> Bool, 
                                   (/=) : a -> a -> Bool,
                                   x `/=` y = not (x == y) }

Obviously this is invalid because you can't set values in record type declarations like that. Does that make sense?

Joe Fiorini

unread,
Dec 12, 2014, 4:55:52 PM12/12/14
to elm-d...@googlegroups.com


Why not create a default version?

type alias RenderOptions =
  { container: Box
  , title: String
  , background: String
  , attributes: List Html.Attribute
  }

-- I don't know what a "Box" is so I used a ? that you can fill in
defaultRenderOptions =
    { container = ?
    , title = "Dummy Title"
    , background = ?
    , attributes = []
    }

myOptions =
    { defualtRenderOptions |
        title <- "My Thing"
    }
 
This may be the best option, my only problem with it is, if I'm using this pattern to implement a shared library, it leaks some implementation detail to the user. Not only do implementers need to remember that they need to call render, but they also have to know about the default options. I've been living in dynamic programming land (Ruby & JS) for a long time, so maybe I've been spoiled by the API design a dynamic language can lead to. Is this how default options would work in other static languages?

Evan Czaplicki

unread,
Dec 12, 2014, 6:26:30 PM12/12/14
to elm-d...@googlegroups.com
What is the exact thing you are trying to do?

Another option could be a function that goes from { x : Maybe a, y : Maybe b, z : Maybe c } to { x : a, y : b, z : c }

But if you say exactly what you want to do it'll be easier to find a way that works well.

--

Joe Fiorini

unread,
Dec 12, 2014, 7:09:36 PM12/12/14
to elm-d...@googlegroups.com
I am writing a routing library for my app that I hope to eventually publish if I can make it work in the ways I want. I'm wrapping https://github.com/tildeio/router.js to do this. Part of what I'm thinking is to provide a type called RouteHandler that has functions on it that hook into the load cycle of a route. In Haskell this might look like:

class RouteHandler a s where
serialize :: s -> String
serialize = defaultSerialize
deserialize :: RouteParams -> s
deserialize = defaultDeserialize
-- probably a few more functions like this

Then in my implementation I'd have something like:

data PostsIndex
instance RouteHandler PostsIndex

And if I didn't need any customizations at all I could get away with not implementing anything. If I need to customize serialize, I could just implement that one.

The closest implementations or RouteHandler I can come up in Elm is:

type alias RouteHandler a s =
{ serialize : s -> String
, deserialize : RouteParams -> s
-- etc
}

But then how do I implement the defaults? Based on your input above it could be something like:

routeHandler =
{ serialize = defaultSerialize
, deserialize = defaultDeserialize
-- etc
}

Then implemented could do:

postsIndex = routeHandler

Or if I need to customize one of the defaults:

postsIndex = { routeHandler | serialize <- mySerialize }

Does that look like it would work? Seems like a reasonable API to me now that I'm looking at it.

Evan Czaplicki

unread,
Dec 12, 2014, 9:25:43 PM12/12/14
to elm-d...@googlegroups.com
I don't understand the details exactly, but the last idea seems very plausible. I do something very similar in Text with Style and defaultStyle, and I feel it has worked quite well so far.

The one risk with exposing records is that it can be a breaking change if you extend them, but I don't think we have an answer on whether there is always a right solution to this risk. I can say though that it makes sense to me to try it the default record way and see how it goes :)

Does that clear up the initial question?

I think if there's stuff you want to hide, it's probably all derived properties or things that are static. In both cases, they can be taken out of the "Setting" record. The derived stuff can be derived later, and the static stuff can just be sitting around.

Reply all
Reply to author
Forward
0 new messages