Do people actually use data-default (Data.Default) Default type class in production?

If there are multiple default constructors, then the Default class is not appropriate anyway, so it’s not really germane to the comparison between def :: Foo and defaultFoo. I would only use Default if there’s some obvious choice - e.g. an identity of a relevant monoid operation or an obvious initial configuration or something.

1 Like

That’s already called mempty!

or an obvious initial configuration or something

Can you give an example where there is only one, and can ever be only one, obvious initial configuration?

Not everything with a sensible default is a monoid, but most monoids have a sensible default. You claimed “There is not a priviliged one” and monoids are a family of counterexamples.

Some examples of things with sensible defaults - scores in games, fresh priors, page view counts, sets and maps under many interpretations, program options, and so on.

If you make a mistake and define a default instance for something which actually has multiple defaults, the cost is very low and the re-factor is very easy. You simply delete the default instance and populate a specific value where the compiler tells you to.

1 Like

I’m not sure what you mean. For the “scores in games” example do you mean you would prefer to read def rather than zero or initial (with or without :: Score on each expression)?

I would prefer to read & write nothing at all, for example

data GameState = GameState Score Powerups ... deriving (Generic, Default)
1 Like

I’m not sure I understand. Even if def is defined for you automatically you still have to use it somewhere. I’m wondering whether in some tangle of code you prefer to read def :: GameState rather than initalGameState. I don’t think I do! Why should I think def means “initial” in this context, as opposed to “a useful starting point for a demo” or “winning game state” or any number of other possible special GameStates?

1 Like

Can you demonstrate how you’d use Default in this GameState example? I just tried and this is the most terse example I could come up with:

{-# LANGUAGE DeriveAnyClass     #-}
{-# LANGUAGE DeriveGeneric      #-}
{-# LANGUAGE StandaloneDeriving #-}

import           Data.Default
import           GHC.Generics

newtype Score = Score Int deriving (Show, Generic)

deriving instance Default Score

data Powerups
    = Strength
    | Speed
    deriving (Show, Generic)

instance Default Powerups where
    def = Strength

data GameState
    = GameState Score Powerups
    deriving (Show, Generic, Default)

main :: IO ()
main = do
    print (def :: GameState)

Running this produces:

$ runghc Main.hs
GameState (Score 0) Strength

Maybe I’ve overlooking something, but there’s several issues I see with this:

  • It requires using some advanced type extensions that in my experience I don’t use otherwise in application development
  • The default for Score requires using a standalone instance
  • In the few minutes I spent on this, I couldn’t figure out how to get a derived Default instance to compile for Powerups, so I just ended up having to define it manually
  • It uses Generics, which are bad for compilation time
  • there’s no single place I can look for what a default GameState is, and the default values for each component of GameState are defined in separate locations

By contrast, here’s my “simple” implementation:

newtype Score = Score Int deriving (Show)

data Powerups
    = Strength
    | Speed
    deriving (Show)

data GameState
    = GameState Score Powerups
    deriving (Show)

defaultGameState :: GameState
defaultGameState =
    GameState (Score 0) Strength

main :: IO ()
main = do
    print defaultGameState

The above:

  • requires no extensions
  • requires no imports
  • defines the default GameState in a single place

Again, maybe I’ve overlooking something but writing out this example strongly reinforces why I don’t like the Default typeclass.

1 Like

Plural powerups - I meant something like newtype Powerups = Powerups (Set Powerup) deriving newtype Default

I always have these enabled. They rock.

newtype Score = Score Int deriving newtype Default

or

newtype Score = Score Int deriving Generic deriving anyclass Default

(You don’t even have to specify anyclass but GHC will warn if there’s an ambiguity between anyclass-deriving and newtype-deriving)

Hopefully it’s clearer when Powerups represents a plural of Powerup

My goal is to simplify my life, not the compiler’s. I can’t bikeshed over the marginal 2ms (or whatever) it takes to compile a generic class definition like this.

Most likely you can intuit the default value by looking at its type definition. If you can’t, it’s probably not suitable for Default.

Also, this objection (along with many others in this thread) is true for basically any typeclass. “I can’t automatically know the definition of show from its invocation here” - that’s OK.

This scales poorly when you have lots of nested objects and/or high arity.

1 Like

You’re right, my mistake. This makes more sense.

It’s easy to hand wave this away until you work on a project that has 700 modules and requires 18 GB of RAM and 20 minutes to compile (this isn’t theoretical - I speak from experience). Generics in a project tend to significantly increase the compile time and it’s not easy to to dump them in pursuit of faster compile times once they’ve become pervasive in your code base. This is a real problem for production users of Haskell, which is the audience that the question in this thread is posed to.

Default values sometimes only make sense in a certain context. A default game state may include a specific score, but I don’t think that’s the same as an independent “default score” that makes sense outside of the default game state.

For example, I may want to reuse the same Score type when initializing two different types of game states, one of which counts up from 0 and the other of which counts down from 301 (let’s call them PoolGameState and DartsGameState in my “Virtual Bar Games” program that lets you play several types of games).

1 Like

The performance point is interesting - do you know of any efforts to benchmark the real-world impact of generics on compile times?

On the one hand, I’m happy to pay a few hundred bucks for some more RAM if it saves me a few hours of my life, but OTOH if I just lose it again in compile time it’s not much of a win.