I can think of three ways to define a geometric point as an algebraic data type in Haskell. I want to define it as an algebraic data type for better documentation.
As a tuple type:
data Point a = Point a a
As a tuple:
data Point a= Point (a,a)
As a record:
data Point a = Point { pointx :: a, pointy :: a}
Which is better design/style and why?
I think this depends mostly on your use-case at hand.
Though I'd never use data Point = Point (a, a)
, because this introduces one layer of indirection, which bloats the data structure - by two machine words - one for the Point
and one for the pointer pointing to the tuple inside it.
Better use type Point a = (a,a)
which is just an alias or newtype Point a = Point (a,a)
which gets optimized away at compile time, so you have the benefits in this case.
The most flexible (for some definitions of flexible) is the last approach, though I'd call the records pr1
and pr2
for 'projection to the first/second component'. This approach also lends itself nicely to using lenses.
As @nikitavolkov said - it is a good idea to have strict data fields for performance reasons, but as I like records I'd ultimately go with
{-# LANGUAGE TemplateHaskell -#}
{-# LANGUAGE DeriveFoldable -#}
{-# LANGUAGE DeriveFunctor -#}
{-# LANGUAGE DeriveTraversable -#}
{-# LANGUAGE RecordWildCards #-}
import Control.Lens
data Point a = Point { _pr1 :: !a
, _pr2 :: !a
} deriving (..)
$(makeLenses ''Point)
which gets you the benefit of using norm Point{..} = sqrt (_pr1^2 + _pr2^2)
with RecordWildCards
and all the niceties of lenses.
Just recently I found the strict package, which provides strict datatytpes as well as strict IO actions. One thing that stuck out to me was the following from Data.Strict.Maybe
Note that strict Maybe is not a monad since
return ⊥ >>= f = ⊥
which is not necessarily the same asf ⊥
.
Which I think is applicable to strict pairs as well - so keep that in mind when defining and/or using strict data types.
Records are a controversial subject in Haskell. There is a consensus in the community that the existing implementation is suboptimal due to many reasons and things are being done to fix that.
Now, since the existing records implementation is suboptimal, many Haskellers tend to never use them. I'm one of those.
I would recommend going with the following:
{-# LANGUAGE DeriveFoldable, DeriveFunctor, DeriveTraversable #-}
data Point a =
Point !a !a
deriving (Functor, Foldable, Traversable)
This will give you free instances of some standard classes and strict data-structures are a recommended default.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With