Haskell Typeclass Inspection - haskell

I would like to use haskell to implement a game, and would like to use a system of type classes to implement the item system. It would work something like this:
data Wood = Wood Int
instance Item Wood where
image a = "wood.png"
displayName a = "Wood"
instance Flammable Wood where
burn (Wood health) | health' <= 0 = Ash
| otherwise = Wood health'
where health' = health - 100
where the Item and Flammable classes are something like this:
class Item a where
image :: a -> String
displayName :: a -> String
class Flammable a where
burn :: (Item b) => a -> b
To do this, I would need a way to detect whether a value is an instance of a type class.
The Data.Data module gives a similar functionality so that leads me to believe that this is possible.

Type classes are probably the wrong way to go here. Consider using plain algebraic data types instead, for example:
data Item = Wood Int | Ash
image Wood = "wood.png"
image Ash = ...
displayName Wood = "Wood"
displayName Ash = "Ash"
burn :: Item -> Maybe Item
burn (Wood health) | health' <= 0 = Just Ash
| otherwise = Just (Wood health')
where health' = health - 100
burn _ = Nothing -- Not flammable
If this makes it too hard to add new items, you can instead encode the operations in the data type itself.
data Item = Item { image :: String, displayName :: String, burn :: Maybe Item }
ash :: Item
ash = Item { image = "...", displayName = "Ash", burn :: Nothing }
wood :: Int -> Item
wood health = Item { image = "wood.png", displayName = "Wood", burn = Just burned }
where burned | health' <= 0 = ash
| otherwise = wood health'
health' = health - 100
However, this makes it harder to add new functions. The problem of doing both at the same time is known as the expression problem. There is a nice lecture on Channel 9 by Dr. Ralf Lämmel where he explains this problem more in depth and discusses various non-solutions, well worth the watch if you have time.
There are approaches to solving it, but they are considerably more complex than the two designs I've illustrated, so I recommend using one of those if it fits your needs, and not worrying about the expression problem unless you have to.

Here's the problem:
burn :: (Item b) => a -> b
What this means is that the result value of burn must be polymorphic. It must be able to fill any hole for any instance of Item.
Now, it's quite apparent you're trying to write something like this (in imaginary OO language with interfaces and subclassing):
Interface Item {
String getImage();
String getDisplayName();
}
Interface Flammable {
Item burn();
}
In this sort of code, you're saying that burn will produce some item, without any guarantees about what kind of item it is. This is the difference between "for all" and "there exists". What you wanted to express in the Haskell code was "there exists", but what you actually expressed was "for all".
Now if you're really sure you want to do "there exists" functionality, you can take a look at using Existential Types. But beware. If you are planning on writing code like this:
if (foo instanceof Flammable) {
...
}
Then you are almost certainly doing it wrong, and will run into much pain and agony. Instead consider hammar's suggested alternatives.

Related

Sum Types vs. Type Classes vs. Records

Which of the following three approaches would be the most idiomatic to implement some kind of rarity for items in an RPG?
The sum type approach "feels" like it's the right one since rarities seem like a "closed set" (or immutable? idk if that's the right word).
But if this were defined in a library then I would not be able to add any more rarities which seems odd.
data Rarity = Rarity { symbol :: String, value :: Int }
common = Rarity "C" 1
rare = Rarity "R" 2
legendary = Rarity "L" 3
data Rarity = Common | Rare | Legendary
symbol :: Rarity -> String
symbol Common = "C"
symbol Rare = "R"
symbol Legendary = "L"
value :: Rarity -> Int
value Common = 1
value Rare = 2
value Legendary = 3
class Rarity r where
symbol :: r -> String
value :: r -> Int
data Common = Common
instance Rarity Common where
symbol _ = "C"
value _ = 1
data Rare = Rare
instance Rarity Rare where
symbol _ = "R"
value _ = 2
data Legendary = Legendary
instance Rarity Legendary where
symbol _ = "L"
value _ = 3
The type class approach you have shown is not very ergonomic in practice.
Presumably you want to have items with a rarity attribute. But what type should the rarity field be? Common, Rare, and Legendary are all separate types, so you can't just have data Item = Item { ..., rarity :: Rarity } (Not to mention the supposed additional rarity levels added by the client program, if this is in a library).
You can have data Item r = Item { ..., rarity :: r }, but now the type of a list (or any other generic collection) of items has to specify what single rarity level all the items in it have (e.g. [Item Common]). That isn't how you want to use rarities in practice (since e.g. a player's inventory can contain items of different rarities!).
You can work around that by using an existential type:
{-# LANGUAGE GADTs #-}
data Item
where Item :: Rarity r =>
{ ...
, rarity :: r
}
-> Item
But now this is basically isomorphic to your first proposal (with data Rarity = Rarity { symbol :: String, value :: Int }), only way more complicated to use. Since you can't do anything with a value of an unknown type that is constrained to be in the Rarity type class other than call Rarity methods, and all those do is get you a String and an Int, you might has well have just used a record of a String and an Int to begin with, and saved all the extensions and boilerplate.
So now we're down to the first two versions: record or sum type.
A potential advantage of the record version is that you (or any client code) can come up with arbitrary new rarity levels. A big potential disadvantage is that you (or any client code) can come up with arbitrary new rarity levels, without any guarantee of consistency with rarity levels used in other items.
Whether that's a feature or a problem really depends on how you intend to process the String and Int that make up a rarity level. If you can truly write engine code in your RPG library that handles those completely agnosticaly of any properties between them, then the record is the right way to go. But my instinct is for RPG rarity levels, you won't do that. You'll rely on an ordering, you'll rely on a given String rarity code corresponding to the same Int value each time you see it, etc etc.
I would go for the static sum type when writing a individual RPG. It just fits my conception of rarity levels better (as you say, within one game it normally should be a closed set, not arbitrarily extensible).
For writing an RPG library, I would use a type class, but differently than you used it.
class Rarity r where
symbol :: r -> String
value :: r -> Int
data GameOneRarity = Common | Rare | Legendary
instance Rarity GameOneRarity
where symbol Common = "C"
symbol Rare = "R"
symbol Legendary = "L"
value Common = 1
value Rare = 2
value Legendary = 3
data GameTwoRarity = Boring | Cool | Awesome
instance Rarity GameTwoRarity
where symbol Boring = "B"
symbol Cool = "C"
symbol Awesome = "A"
value Boring = 100
value Cool = 1000
value Awesome = 10000000
I don't have a separate type for each new rarity level, I have a separate type for each rarity scheme. The idea would be for each game to define its own rarity type. An individual set of rarities is not extensible, but the library code can handle arbitrary schemes with whatever rarity levels the game designer wants (so long as they can implement symbol and value for them).
The inventory system I write generically in the RPG library can use types like [Item r] to ensure that all of the items it considers together use the same rarity system (avoiding the possibility of having to answer non-sensible questions like whether a Legendary item from one game is more or less valuable than a Cool item from a different game) but each item can have its own rarity level within that system.

Haskell conversion between types

Again stuck on something probably theoretical. There are many libraries in Haskell, i'd like to use less as possible. If I have a type like this:
data Note = Note { _noteID :: Int
, _noteTitle :: String
, _noteBody :: String
, _noteSubmit :: String
} deriving Show
And use that to create a list of [Note {noteID=1...}, Note {noteID=2...}, ] et cetera. I now have a list of type Note. Now I want to write it to a file using writeFile. Probably it ghc will not allow it considering writeFile has type FilePath -> String -> IO (). But I also want to avoid deconstructing (writeFile) and constructing (readFile) the types all the time, assuming I will not leave the Haskell 'realm'. Is there a way to do that, without using special libs? Again: thanks a lot. Books on Haskell are good, but StackOverflow is the glue between the books and the real world.
If you're looking for a "quick fix", for a one-off script or something like that, you can derive Read in addition to Show, and then you'll be able to use show to convert to String and read to convert back, for example:
data D = D { x :: Int, y :: Bool }
deriving (Show, Read)
d1 = D 42 True
s = show d1
-- s == "D {x = 42, y = True}"
d2 :: D
d2 = read s
-- d2 == d1
However, please, please don't put this in production code. First, you're implicitly relying on how the record is coded, and there are no checks to protect from subtle changes. Second, the read function is partial - that is, it will crash if it can't parse the input. And finally, if you persist your data this way, you'll be stuck with this record format and can never change it.
For a production-quality solution, I'm sorry, but you'll have to come up with an explicit, documented serialization format. No way around it - in any language.

"SubTypes" with own Show and usable in other Module

I am pretty sure my problem stems from the fact that I am new to Haskell and do not understand the type system completely.
I will use the example used in Haskell's wiki on the type topic
The following Type is defined
data Suit = Club | Diamond | Heart | Spade
Now the first issue is, I would like to implement Show for every "sub-type" (Club, Diamond, Heart, Spade) but this doesn't seem to work because it is a DataKinds. So I split it up into their own types. (The properties don't make much sense but I added them to be closer to the real code)
data ClubType = ClubType {
clubName :: String,
icon :: String
}
instance Show ClubType where
show (ClubType cn i) = "name: " ++ cn ++ ", icon: " ++ i
Using them in the Suit type
data Suit = Club ClubType
| Diamond DiamondType
| Heart HeartType
| Spade SpadeType
Now I want to use Suit and the "Sub-Types" (Club, Diamond, Heart, Spade) in a different module. I exported just Suit (..).
Usage
import Module1 (Suit(..))
getSuit :: String -> Suit
getSuit "Club" = getClub
getSuit "Heart" = getHeart
...
getClub :: () -> Club
Now again Club, Heart, Diamond and Spade cannot be used because they are DataKinds. How do I use the "sub-types"? Do I need to export all types? If I do, does it conform with the return type of getSuit?
(Sorry if the example doesn't make a whole lot of sense, but I hope you can follow my desired result)
thanks
Yes it sounds like you don't really understand the type system. I will try my best to clarify where I think you are going wrong. In your example
data Suit = Club | Diamond | Heart | Spade
There is only one type, and that is Suit. Club, Diamond, etc. are not types at all, and you will confuse yourself if you refer to them as "sub-types". The proper name for them is constructors, and they are essentially values of type Suit
Club :: Suit
Diamond :: Suit
...
So you can't really implement a Show instance for "each one", you can only implement a Show instance for the whole type Suit, in the usual way, by pattern matching on the constructors:
instance Show Suit where
show Club = "Clubs"
show Diamond = "Diamonds"
show Heart = "Heart"
show Spade = "Spade"
This defines a single function show on the type Suit -> String, again there is no "sub-type" anything going on. I wonder if this was all you were looking for.
Since you mention datakinds, using the constructors at the type level still does not make them types -- they are still values of type Suit. An example of using them at the type level is to index a GADT by these:
data Card :: Suit -> * where
QueenOfSpades :: Card Spade
OtherCard :: Int -> Card s -- we'd want to encode the suit s at the data
-- level too using a singleton, out of scope
-- for this post
But this is fairly advanced stuff and probably not what you are looking for.
I hope this clarifies things a little.

Cases of types vs fields of records in Haskell

The two following pieces of code seem really similar. However there must be some differences and I am hoping that someone could point them out.
data Animal = Cat | Dog
speak :: Animal -> String
speak Cat = "meowh"
speak Dog = "wouf"
and
data Animal = Animal { speak :: String }
cat = Animal { speak = "meowh"}
dog = Animal { speak = "wouf" }
Good question! You've struck at the heart of one of the fundamental problems of software engineering. Per Wadler, it's called the Expression Problem, and, roughly summarised, the question is:
Should it be easy to add new operations, or new types of data?
Your first example makes it easy to add new operations to existing animals. We can process Animal values in all sorts of ways without changing the definition of Animal:
numberOfTeeth :: Animal -> Int
numberOfTeeth Cat = 30
numberOfTeeth Dog = 42
food :: Animal -> String
food Cat = "Fish"
food Dog = "Sausages" -- probably stolen from a cartoon butcher
The downside is that it's hard to add new types of animal. You have to add new constructors to Animal and change all the existing operations:
data Animal = Cat | Dog | Crocodile
speak :: Animal -> String
speak Cat = "miaow"
speak Dog = "woof"
speak Crocodile = "RAWR"
numberOfTeeth :: Animal -> Int
numberOfTeeth Cat = 30
numberOfTeeth Dog = 42
numberOfTeeth Crocodile = 100000 -- I'm not a vet
food :: Animal -> String
food Cat = "Fish"
food Dog = "Sausages"
food Crocodile = "Human flesh"
Your second example flips the matrix, making it easy to add new types,
crocodile = Animal { speak = "RAWR" }
but hard to add new functions - it means adding new fields to Animal and updating all the existing animals.
data Animal = Animal {
speak :: String,
numberOfTeeth :: Int,
food :: String
}
cat = Animal {
speak = "miaow",
numberOfTeeth = 30,
food = "Fish"
}
dog = Animal {
speak = "woof",
numberOfTeeth = 42,
food = "Sausages"
}
crocodile = Animal {
speak = "RAWR",
numberOfTeeth = 100000,
food = "Human flesh"
}
Don't underestimate how big of a deal the Expression Problem is! If you're working on a published library, you may find yourself contending with operations or types defined by someone you've never met in a codebase you can't change. You have to think carefully about the way you expect people to use your system, and decide how to orient the design of your library.
Over the years, smart people have invented lots of clever ways of solving the Expression Problem, to support new operations and new types. These solutions tend to be complicated, using the most advanced features of the most modern programming languages. In the real world, this is just another trade-off engineers have to consider - is solving the Expression Problem worth the code-complexity it'll cause?
First, let's assign the types the names, which closer reflect their essence:
data AnimalType =
Cat | Dog
newtype Phrase =
Phrase { phrase :: String }
It already becomes evident that they are very different and clearly isolatable.
A phrase here is a way more general thing. It can be spoken not just by animals, but by robots at least as well. But more so it can not only be spoken, but also be transformed with operations like upper-casing and etc. Such operations would make no sense for the AnimalType type.
AnimalType OTOH has its own benefits. By matching the type, you can pick a type of food the animal needs or its size and etc. You can't do that on Phrase.
You can also have both types coexist in isolation in your application and have a transformation (and, hence, a dependency) from the more specific to the more general one:
module Animal where
import qualified Phrase
speakPhrase :: Animal -> Phrase.Phrase
speakPhrase =
Phrase.Phrase . speak
What's causing your confusion is that your problem lacks the context of an application. Once you'll provide it to yourself, you'll get the info on what you actually need this thing to do and which data it'll operate on.

Haskell: Confusion with own data types. Record syntax and unique fields

I just uncovered this confusion and would like a confirmation that it is what it is. Unless, of course, I am just missing something.
Say, I have these data declarations:
data VmInfo = VmInfo {name, index, id :: String} deriving (Show)
data HostInfo = HostInfo {name, index, id :: String} deriving (Show)
vm = VmInfo "vm1" "01" "74653"
host = HostInfo "host1" "02" "98732"
What I always thought and what seems to be so natural and logical is this:
vmName = vm.name
hostName = host.name
But this, obviously, does not work. I got this.
Questions
So my questions are.
When I create a data type with record syntax, do I have to make sure that all the fields have unique names? If yes - why?
Is there a clean way or something similar to a "scope resolution operator", like :: or ., etc., so that Haskell distinguishes which data type the name (or any other none unique fields) belongs to and returns the correct result?
What is the correct way to deal with this if I have several declarations with the same field names?
As a side note.
In general, I need to return data types similar to the above example.
First I returned them as tuples (seemed to me the correct way at the time). But tuples are hard to work with as it is impossible to extract individual parts of a complex type as easy as with the lists using "!!". So next thing I thought of the dictionaries/hashes.
When I tried using dictionaries I thought what is the point of having own data types then?
Playing/learning data types I encountered the fact that led me to the above question.
So it looks like it is easier for me to use dictionaries instead of own data types as I can use the same fields for different objects.
Can you please elaborate on this and tell me how it is done in real world?
Haskell record syntax is a bit of a hack, but the record name emerges as a function, and that function has to have a unique type. So you can share record-field names among constructors of a single datatype but not among distinct datatypes.
What is the correct way to deal with this if I have several declarations with the same field names?
You can't. You have to use distinct field names. If you want an overloaded name to select from a record, you can try using a type class. But basically, field names in Haskell don't work the way they do in say, C or Pascal. Calling it "record syntax" might have been a mistake.
But tuples are hard to work with as it is impossible to extract individual parts of a complex type
Actually, this can be quite easy using pattern matching. Example
smallId :: VmInfo -> Bool
smallId (VmInfo { vmId = n }) = n < 10
As to how this is done in the "real world", Haskell programmers tend to rely heavily on knowing what type each field is at compile time. If you want the type of a field to vary, a Haskell programmer introduces a type parameter to carry varying information. Example
data VmInfo a = VmInfo { vmId :: Int, vmName :: String, vmInfo :: a }
Now you can have VmInfo String, VmInfo Dictionary, VmInfo Node, or whatever you want.
Summary: each field name must belong to a unique type, and experienced Haskell programmers work with the static type system instead of trying to work around it. And you definitely want to learn about pattern matching.
There are more reasons why this doesn't work: lowercase typenames and data constructors, OO-language-style member access with .. In Haskell, those member access functions actually are free functions, i.e. vmName = name vm rather than vmName = vm.name, that's why they can't have same names in different data types.
If you really want functions that can operate on both VmInfo and HostInfo objects, you need a type class, such as
class MachineInfo m where
name :: m -> String
index :: m -> String -- why String anyway? Shouldn't this be an Int?
id :: m -> String
and make instances
instance MachineInfo VmInfo where
name (VmInfo vmName _ _) = vmName
index (VmInfo _ vmIndex _) = vmIndex
...
instance MachineInfo HostInfo where
...
Then name machine will work if machine is a VmInfo as well as if it's a HostInfo.
Currently, the named fields are top-level functions, so in one scope there can only be one function with that name. There are plans to create a new record system that would allow having fields of the same name in different record types in the same scope, but that's still in the design phase.
For the time being, you can make do with unique field names, or define each type in its own module and use the module-qualified name.
Lenses can help take some of the pain out of dealing with getting and setting data structure elements, especially when they get nested. They give you something that looks, if you squint, kind of like object-oriented accessors.
Learn more about the Lens family of types and functions here: http://lens.github.io/tutorial.html
As an example for what they look like, this is a snippet from the Pong example found at the above github page:
data Pong = Pong
{ _ballPos :: Point
, _ballSpeed :: Vector
, _paddle1 :: Float
, _paddle2 :: Float
, _score :: (Int, Int)
, _vectors :: [Vector]
-- Since gloss doesn't cover this, we store the set of pressed keys
, _keys :: Set Key
}
-- Some nice lenses to go with it
makeLenses ''Pong
That makes lenses to access the members without the underscores via some TemplateHaskell magic.
Later on, there's an example of using them:
-- Update the paddles
updatePaddles :: Float -> State Pong ()
updatePaddles time = do
p <- get
let paddleMovement = time * paddleSpeed
keyPressed key = p^.keys.contains (SpecialKey key)
-- Update the player's paddle based on keys
when (keyPressed KeyUp) $ paddle1 += paddleMovement
when (keyPressed KeyDown) $ paddle1 -= paddleMovement
-- Calculate the optimal position
let optimal = hitPos (p^.ballPos) (p^.ballSpeed)
acc = accuracy p
target = optimal * acc + (p^.ballPos._y) * (1 - acc)
dist = target - p^.paddle2
-- Move the CPU's paddle towards this optimal position as needed
when (abs dist > paddleHeight/3) $
case compare dist 0 of
GT -> paddle2 += paddleMovement
LT -> paddle2 -= paddleMovement
_ -> return ()
-- Make sure both paddles don't leave the playing area
paddle1 %= clamp (paddleHeight/2)
paddle2 %= clamp (paddleHeight/2)
I recommend checking out the whole program in its original location and looking through the rest of the lens material; it's very interesting even if you don't end up using them.
Yes, you cannot have two records in the same module with the same field names. The field names are added to the module's scope as functions, so you would use name vm rather than vm.name. You could have two records with the same field names in different modules and import one of the modules qualified as some name, but this is probably awkward to work with.
For a case like this, you should probably just use a normal algebraic data type:
data VMInfo = VMInfo String String String
(Note that the VMInfo has to be capitalized.)
Now you can access the fields of VMInfo by pattern matching:
myFunc (VMInfo name index id) = ... -- name, index and id are bound here

Resources