Mrk Sef
Mrk Sef

Reputation: 8062

Understading newtype and when to use it

I'm pretty new to purescript and functional programming

Newtypes are distinct from the point of view of the type system. This gives an extra layer of type safety.

This is what the opening ~50 lines of code look like for my Sudoku Solver (so far):

newtype Index = Index Int
newtype Option = Option Int
newtype Column = Column Int
newtype Row = Row Int
newtype Box = Box Int
newtype Cell = Cell Int

derive newtype instance eqIndex :: Eq Index
derive newtype instance eqOption :: Eq Option
derive newtype instance eqColumn :: Eq Column
derive newtype instance eqRow :: Eq Row
derive newtype instance eqBox :: Eq Box
derive newtype instance eqCell :: Eq Cell

derive newtype instance semiringIndex :: Semiring Index
derive newtype instance semiringOption :: Semiring Option
derive newtype instance semiringColumn :: Semiring Column
derive newtype instance semiringRow :: Semiring Row
derive newtype instance semiringBox :: Semiring Box
derive newtype instance semiringCell :: Semiring Cell

derive newtype instance ringIndex :: Ring Index
derive newtype instance ringOption :: Ring Option
derive newtype instance ringColumn :: Ring Column
derive newtype instance ringRow :: Ring Row
derive newtype instance ringBox :: Ring Box
derive newtype instance ringCell :: Ring Cell

derive newtype instance commutativeRingIndex :: CommutativeRing Index
derive newtype instance commutativeRingOption :: CommutativeRing Option
derive newtype instance commutativeRingColumn :: CommutativeRing Column
derive newtype instance commutativeRingRow :: CommutativeRing Row
derive newtype instance commutativeRingBox :: CommutativeRing Box
derive newtype instance commutativeRingCell :: CommutativeRing Cell

derive newtype instance euclideanRingIndex :: EuclideanRing Index
derive newtype instance euclideanRingOption :: EuclideanRing Option
derive newtype instance euclideanRingColumn :: EuclideanRing Column
derive newtype instance euclideanRingRow :: EuclideanRing Row
derive newtype instance euclideanRingBox :: EuclideanRing Box
derive newtype instance euclideanRingCell :: EuclideanRing Cell

derive newtype instance showIndex :: Show Index
derive newtype instance showOption :: Show Option
derive newtype instance showColumn :: Show Column
derive newtype instance showRow :: Show Row
derive newtype instance showBox :: Show Box
derive newtype instance showCell :: Show Cell

class IsInt a where toInt :: a -> Int
instance ciInt :: IsInt Int where toInt a = a
instance ciIndex :: IsInt Index where toInt (Index a) = a
instance ciOption :: IsInt Option where toInt (Option a) = a
instance ciColumn :: IsInt Column where toInt (Column a) = a
instance ciRow :: IsInt Row where toInt (Row a) = a
instance ciBox :: IsInt Box where toInt (Box a) = a
instance ciCell :: IsInt Cell where toInt (Cell a) = a

Likely, I'll soon need Ord and a few others as well.

So right now this stops me from from adding/subtracting an index and a row by accident, or mixing up parameters to functions that manipulate these types. It's also a pretty big amount of boilerplate in order to effectively have 7 names for Int.

I'm guessing this is overkill and I probably shouldn't bother? I could use type synonyms to keep the code looking descriptive and lose some type safety.

Is there another/better way?

Upvotes: 0

Views: 144

Answers (1)

Fyodor Soikin
Fyodor Soikin

Reputation: 80880

As with everything in life, there is no recipe. It's a spectrum. No type safety at all on one extreme, or drown in newtypes on the other. The truth, as always, lies somewhere in the middle.

But to tell you anything more specific, I'd need to know the larger design. It's kind of impossible to say just from those definitions. It might be that you actually do need all of them, in which case... well... you do need all of them.

But in my experience, that's usually not the case. Some of the questions to ponder could be:

  • Do all of them really need Ring and CommutativeRing? Somehow I doubt that.
  • Do all of them really need to be separate, or could some be combined in one type? For example, Row and Column could be a Point together.
  • If you want your type to have all characteristics of a number, then perhaps it should actually be a number?
  • Could the type safety be achieved by using named parameters (via records) instead?

The way I usually go about these things is to start barebones and then add support for stuff as need arises. You will probably be surprised how little need will arise in practice.

And this point applies to using newtypes in the first place. Is it really that probable that you might mix up rows and columns? Or are you just trying to plug every possible hole just in case? If it's the latter, then yes, you do end up with a ton of hole plugs, there's no surprise there.

Upvotes: 3

Related Questions