Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it bad form to make new types/datas for clarity? [closed]

I would like to know if it is bad form to do something like this:

data Alignment = LeftAl | CenterAl | RightAl
type Delimiter = Char
type Width     = Int

setW :: Width -> Alignment -> Delimiter -> String -> String

Rather than something like this:

setW :: Int -> Char -> Char -> String -> String

I do know that remaking those types effectively does nothing but take up a few lines in exchange for clearer code. However, if I use the type Delimiter for multiple functions, this would be much clearer to someone importing this module, or reading the code later.

I am relatively new to Haskell so I do not know what is good practice for this type of stuff. If this is not a good idea, or there is something that would improve clarity that is preferred, what would that be?

like image 607
BryceTheGrand Avatar asked Jul 16 '19 16:07

BryceTheGrand


People also ask

Which type of chart will be most effective?

Line charts are the most effective chart for displaying time-series data. They can handle a ton of data points and multiple data series, and everyone knows how to read them.

Which type of graph is better for showing distribution of data?

Scatter plots are best for showing distribution in large data sets.

Which chart type would be a better choice when you have long category labels?

If you have long category names, it is best to use bar charts because they give more space for long text. You should also use bar charts, instead of column charts, when the number of categories is greater than seven (but not more than fifteen) or for displaying a set with negative numbers.

What is the best design for input validation?

The best position for validationInstant validation is best positioned to the right hand side of the input, or failing that immediately below. This works best in principle for creating the conversation between user and for that manages to gamify the often tedious process of form inputs.


4 Answers

You're using type aliases, they only slightly help with code readability. However, it's better to use newtype instead of type for better type-safety. Like this:

data Alignment = LeftAl | CenterAl | RightAl
newtype Delimiter = Delimiter { unDelimiter :: Char }
newtype Width     = Width { unWidth :: Int }

setW :: Width -> Alignment -> Delimiter -> String -> String

You will deal with extra wrapping and unwrapping of newtype. But the code will be more robust against further refactorings. This style guide suggests to use type only for specializing polymorphic types.

like image 126
Shersh Avatar answered Sep 27 '22 17:09

Shersh


I wouldn't consider that bad form, but clearly, I don't speak for the Haskell community at large. The language feature exists, as far as I can tell, for that particular purpose: to make the code easier to read.

One can find examples of the use of type aliases in various 'core' libraries. For example, the Read class defines this method:

readList :: ReadS [a]

The ReadS type is just a type alias

type ReadS a = String -> [(a, String)]

Another example is the Forest type in Data.Tree:

type Forest a = [Tree a]

As Shersh points out, you can also wrap new types in newtype declarations. That's often useful if you need to somehow constrain the original type in some way (e.g. with smart constructors) or if you want to add functionality to a type without creating orphan instances (a typical example is to define QuickCheck Arbitrary instances to types that don't otherwise come with such an instance).

like image 32
Mark Seemann Avatar answered Sep 27 '22 19:09

Mark Seemann


Using newtype—which creates a new type with the same representation as the underlying type but not substitutable with it— is considered good form. It's a cheap way to avoid primitive obsession, and it's especially useful for Haskell because in Haskell the names of function arguments are not visible in the signature.

Newtypes can also be a place on which to hang useful typeclass instances.

Given that newtypes are ubiquitous in Haskell, over time the language has gained some tools and idioms to manipulate them:

  • Coercible A "magical" typeclass that simplifies conversions between newtypes and their underlying types, when the newtype constructor is in scope. Often useful to avoid boilerplate in function implementations.

    ghci> coerce (Sum (5::Int)) :: Int

    ghci> coerce [Sum (5::Int)] :: [Int]

    ghci> coerce ((+) :: Int -> Int -> Int) :: Identity Int -> Identity Int -> Identity Int

  • ala. An idiom (implemented in various packages) that simplifies the selection of a newtype that we might want to use with functions like foldMap.

    ala Sum foldMap [1,2,3,4 :: Int] :: Int

  • GeneralizedNewtypeDeriving. An extension for auto-deriving instances for your newtype based on instances available in the underlying type.

  • DerivingVia A more general extension, for auto-deriving instances for your newtype based on instances available in some other newtype with the same underlying type.

like image 31
danidiaz Avatar answered Sep 27 '22 17:09

danidiaz


One important thing to note is that Alignment versus Char is not just a matter of clarity, but one of correctness. Your Alignment type expresses the fact that there are only three valid alignments, as opposed to however many inhabitants Char has. By using it, you avoid trouble with invalid values and operations, and also enable GHC to informatively tell you about incomplete pattern matches if warnings are turned on.

As for the synonyms, opinions vary. Personally, I feel type synonyms for small types like Int can increase cognitive load, by making you track different names for what is rigorously the same thing. That said, leftaroundabout makes a great point in that this kind of synonym can be useful in the early stages of prototyping a solution, when you don't necessarily want to worry about the details of the concrete representation you are going to adopt for your domain objects.

(It is worth mentioning that the remarks here about type largely don't apply to newtype. The use cases are different, though: while type merely introduces a different name for the same thing, newtype introduces a different thing by fiat. That can be a surprisingly powerful move -- see danidiaz's answer for further discussion.)

like image 23
duplode Avatar answered Sep 27 '22 17:09

duplode