Ultimate Guide to Using Haskell Styles?

So, I am very good at algebraic types and class types, but I am interested in its design and development.

What is the current consensus, if any, in classrooms? Are they evil? Are they comfortable? Should they be used and when?

Here is my case study. I am writing a game in the style of RTS, and I have different "units" (tank, reconnaissance, etc.). Let's say I want to get the maximum amount of health for each unit. My two thoughts on how to determine their types are as follows:

Various ADT constructors:

data Unit = Scout ... | Tank ... maxHealth :: Unit -> Int maxHealth Scout = 10 maxHealth Tank = 20 

Typeclass for Unit, each view is an instance

 class Unit a where maxHealth :: a -> Int instance Unit Scout where maxHealth scout = 10 instance Unit Tank where maxHealth tank = 20 

Obviously, the final product will have more fields and functions. (For example, each unit will have a different position, etc., Therefore, not all functions will be constant).

The trick is that some units may have some functions, but not others. For example, each unit will have a getPosition function, but a tank may have a getArmour function, which does not make sense to a scout without armor.

What is the “generally accepted” way to write this if I want other Haskellers to understand and follow my code?

+4
source share
3 answers

Most Haskell programmers are unhappy with unnecessary classes. This is a sick type of withdrawal; you can't even make a Unit list without tricks; GHC has all the secret vocabulary going through; they somehow complicate the reading of the Chicks; they can lead to a fragile hierarchy ... perhaps others can give you additional reasons. I think a good rule would be to use them when it would be much more painful to avoid them. For example, without Eq you will have to manually pass functions to compare, say, two [[[Int]]] (or use some special run-time tests), which is one of the pain points of ML programming.

Take a look at this blog post . Your first method of using a sum type is fine, but if you want to allow users to moderate the game with new units or something else, I would suggest something like

 data Unit = Unit { name :: String, maxHealth :: Int } scout, tank :: Unit scout = Unit { name = "scout", maxHealth = 10 } tank = Unit { name = "tank", maxHealth = 20 } allUnits = [ scout , tank , Unit { name = "another unit", maxHealth = 5 } ] 

In your example, you need to code somewhere so that the tank has armor, but the scout does not. The obvious possibility is to increase the type of Unit with additional information, such as the Maybe Armor field or the list of special permissions ... there is no need for a specific method.

One heavyweight option, perhaps overkill, is to use a library such as Vinyl, which provides extensible records, giving you a subtype form.

+6
source

I usually use cool classes only when generating and passing instances manually becomes a big problem. In code, I write this almost never.

+3
source

I will not weigh the answer to the final time to use type classes, but I am currently writing a library that uses both methods described for your Unit class. I usually rely on the type of sum, but there is one big advantage that the typeclass method has: it gives you level differences between Unit s.

This forces you to write a little more on your interface, since any function that should be polymorphic over Unit should only use functions defined, ultimately, based on your abstract type. In my case, it is also very important to use type types of Unit type as type parameters in phantom types.

For example, I am writing a Haskell binding to Nanomsg (ZMQ project from the original author of ZMQ). At Nanomsg, you have Socket types that separate views and semantics. Each Socket has exactly one Protocol , and some functions can only be called on the Socket specific Protocol . I could perform these functions with errors or return Maybe s, but instead I defined my Protocol as a separate type, everyone uses a common class.

 class Protocol p where ... data Protocol1 = Protocol1 data Protocol2 = Protocol2 instance Protocol Protocol1 where ... instance Protocol Protocol2 where ... 

and Socket have a phantom parameter of type

 newtype Socket p = Socket ... 

And now I can make this a type error for calling functions in the wrong protocols.

 funOnProto1 :: Socket Protocol1 -> ... 

whereas if Socket was just a sum type, it would be impossible to verify at compile time.

+2
source

Source: https://habr.com/ru/post/1493349/


All Articles