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.
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.
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)
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 GameState
s?
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 forPowerups
, 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 ofGameState
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.
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.
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).
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.