I'm a total beginner with Haskell. While trying to solve some practice exercises on hackerrank I stumbled over an error, which made me wonder about doing it "the right way"(tm).
What I was trying to do was this:
import Data.Matrix
newtype Puzzle = Matrix Char
complete :: Puzzle -> Bool
complete p = '-' `elem` (toList p)
[... more functions on 'Matrix Char']
and this gave me:
Couldn't match expected type ‘Matrix Char’
with actual type ‘Puzzle’
In the first argument of ‘toList’, namely ‘p’
In the second argument of ‘elem’, namely ‘(toList p)’
The obvious solution is of course just using Matrix Char
instead of Puzzle
. But I don't feel that this is an elegant solution. An abstraction into a more specific type feels like the right way to go...
I think a better solution than the one offered by Jeffrey's answer, at least in the case of more substantial code bases than a toy game, is to keep using newtype
but to change the code to this:
import Data.Matrix
newtype Puzzle = Puzzle (Matrix Char)
complete :: Puzzle -> Bool
complete (Puzzle matrix) = '-' `elem` toList matrix
This will allow you to keep using a truly distinct data type as opposed to resorting to a type alias, which doesn't introduce any new types and will allow completely interchangeable use of Puzzle
and Matrix Char
with no added type safety (nor expressiveness).
Also, Jeffrey is right in that newtype
is more similar to data
than type
— newtype
offers some performance optimisations over data
but is more restricted and slightly affects the program evaluation semantics. You're better off reading up on all the various ways of defining types and type aliases in Haskell.
In your case, you might as well substitute data
for newtype
without changing the behavior of your program; the rest of the program should continue to work identically.
See also: Haskell type vs. newtype with respect to type safety