Haskells data types and constructors with functions? - haskell

I'm new to Haskell and I'm looking at basic data types and constructors with functions.
I've done the below code:
data Name = Name String deriving (Show)
data Age = Age Int deriving (Show)
data Iq = Iq Int deriving (Show)
data Language = Language String deriving (Show)
data DataSubject = DSInformation Name Age Iq Language | DSConstruct {name :: String, age :: Int, iq :: Int, language :: String} deriving (Show)
makeDataSubject :: DataSubject -> DataSubject --Take in info and output a record
makeDataSubject (DSInformation (Name n) (Age a) (Iq i) (Language l)) = (DSConstruct {name = n, age = a, iq = i, language = l})
main = do
let x = makeDataSubject $ (DSInformation (Name "Ron") (Age 34) (Iq 100) (Language "French"))
putStrLn $ show x
Runs fine, however it seems overly verbose -- how can I make to make it better?

Most of your data declarations can probably be simple type aliases.
type Name = String
type Age = Int
type Iq = Int
type Language = String
With these aliases, there is no significant difference (record syntax aside) between the two constructors for DataSubject. Get rid of one, and dispense with makeDataSubject. (Unless you want to encapsulate some logic or prevent pattern matching, you don't need a smart constructor to do what you are doing.)
data DataSubject = DS { name :: Name
, age :: Age
, iq :: Iq
, language :: Language
} deriving (Show)
main = do
let x = DS { name="Ron", age=34, iq=100, language="French"}
putStrLn $ show x
If you do want real types, not just aliases, use newtype instead of data.
newtype Name = Name String deriving Show
newtype Age = Age Int deriving Show
newtype Iq = Iq Int deriving Show
newtype Language = Language String deriving Show
data DataSubject = DS { name :: Name
, age :: Age
, iq :: Iq
, language :: Language
} deriving (Show)
main = do
let x = DS { name=Name "Ron", age=Age 34, iq=Iq 100, language=Language "French"}
putStrLn $ show x
You might want to add a smart constructor here, but have it take each piece of data as a separate argument (wrapped or unwrapped), not a single argument that is already the return value up to isomorphism. (That is, your constructor was essentially the identity function other than some repackaging of the input.)
makeDataSubject :: String -> Int -> Int -> String -> DataSubject
makeDataSubject name age iq lang = DS {name=Name name, age=Age age, iq=Iq iq, language=Language lang}
or
makeDataSubject' :: Name -> Age -> Iq -> Language -> DataSubject
makeDataSubject' name age iq lang = DS {name=name, age=age, iq=iq, language=lang}

Unfortunately you are running into one of Haskell's weaknesses: the record system. It would be nice if we had some sort notation to express subject.name and subject.age rather than destructuring explicitly but right now there is no good answer. Work coming down the pipeline in GHC 8 should address the problem soon, however, and there are are all sorts of libraries working in the problem space. But, for this question specifically, we can employ a simple trick: -XRecordWildcards.
{-# LANGUAGE RecordWildCards #-}
module Main where
newtype Name = Name String deriving Show
newtype Age = Age Int deriving Show
newtype Iq = Iq Int deriving Show
newtype Language = Language String deriving Show
data DataSubject =
DSInformation Name Age Iq Language
| DSConstruct {name :: String, age :: Int, iq :: Int, language :: String}
deriving Show
-- | Take in info and output a record
makeDataSubject :: DataSubject -> DataSubject
makeDataSubject (DSInformation (Name name) (Age age) (Iq iq) (Language language)) =
DSConstruct {..}
main :: IO ()
main =
print . makeDataSubject $ DSInformation (Name "Ron") (Age 34) (Iq 100) (Language "French")
By destructuring into the names of the fields, {..} will pick up on those bindings in scope to automatically populate the fields. You will absolutely want to turn on -Wall -Werror during compilation because it will be now more than ever easier to misspell something and forget to populate a field and then you end up with a partial record (another wart of the records system) where some fields are left undefined.

Related

Overloading Show and Num in typeclass makes execution hang indefinitely

I am making assigment on Haskell, and almost solved it.. Feels like I miss some last step, but cant put my finger on, what do I do wrong.
First my task:
Now, insted of using type synonyms, define data types CountryCode and PhoneNo so that both of them have a value constructor that takes an integer.
Derive an instance for Eq, Ord and Show for PhoneType.
Derive instances for Eq and Ord for CountryCode and PhoneNo and make Show instances for them so that:
CountryCode: print '+' in front of the number.
PhoneNo: print only the number.
Make a function for both of them (toCountryCode and toPhoneNo) that takes an Integer and throws an error if the integer is negative otherwise it creates the value.
If CountryCode is negative, the error should be "Negative country code" and if PhoneNo is negative, the error should be "Negative phone number" and you should follow these literally to pass the automatic testing.
Again, using the record syntax, define Phone type for phone numbers that has only one value constructor with fields
phoneType :: PhoneType,
countryCode :: CountryCode, (This time the type defined as above)
phoneNo :: PhoneNo. (This time the type defined as above)
Derive an instance for Eq and Ord for the record, but for Show make it "pretty-print" the infromation in this form:
e.g. +358 123456789 (WorkLandline)
Now my take on the task:
data PhoneType = WorkLandline|PrivateMobile|WorkMobile|Other deriving (Read,Show,Eq)
newtype PlainString = PlainString String
instance Show PlainString where
show (PlainString s) = s
--instance Num Int where
-- (*)=int_times
-- (+)=int_plus
data CountryCode = CountryCode
{
cCode :: Int
} deriving (Read,Eq)
instance Show CountryCode where
show (CountryCode a)=(show (PlainString "+")) ++ (show a)
instance Num CountryCode where
(*) a b=a*b
(+) a b=a+b
(-) a b=a-b
fromInteger x=fromInteger x
toCountryCode :: Int->CountryCode
toCountryCode ccode
| ccode<0=error "Negative country code"
|otherwise = CountryCode ccode
data PhoneNo = PhoneNo
{
pNo :: Int
} deriving (Read,Eq)
instance Show PhoneNo where
show (PhoneNo a)=show a
instance Num PhoneNo where
(*) a b=a*b
(+) a b=a+b
(-) a b=a-b
fromInteger x= fromInteger x
toPhoneNo :: Int -> PhoneNo
toPhoneNo pno
| pno<0=error "Negative phone number"
|otherwise = PhoneNo pno
data Phone = Phone
{
phoneType :: PhoneType,
countryCode :: CountryCode,
phoneNo :: PhoneNo
} deriving (Read,Eq)
instance Show Phone where
show (Phone a b c)=show a
makePhone :: PhoneType -> CountryCode -> PhoneNo -> Phone
makePhone phonetype countrycode phoneno
|otherwise = Phone phonetype countrycode phoneno
This version kinda workds with:
makePhone Other 1 1
, which shows Other.
However, if i modify this to:
show (Phone a b c)=show b
or make sane show like asked in task - program hangs indefinitely. Same goes for show c
What do I do wrong?
Implementations like:
instance Num CountryCode where
(*) a b = a*b
(+) a b = a+b
(-) a b = a-b
fromInteger x = fromInteger x
make not much sense, since you here define that you can add two CountryCodes together, by adding these together... This thus results in infite recursion.
You can define addition, multiplication, etc. by unpacking the value wrapped in the CountryCode data constructor, perform the arithmetic, and then wrap it in a CountryCode data constructor:
instance Num CountryCode where
CountryCode a * CountryCode b = CountryCode (a * b)
CountryCode a + CountryCode b = CountryCode (a + b)
CountryCode a - CountryCode b = CountryCode (a - b)
fromInteger x = CountryCode (fromInteger x)
The same happens with the instance Num PhoneNo.
You can use GeneralizedNewtypeDeriving to derive the following instances, but they must be newtypes for it to work:
Show PlainString
Num CountryCode
Show PhoneNo, Num PhoneNo
I suggest using DerivingStrategies to make it explicit what method of deriving you are using: stock uses the deriving mechanism built in to GHC, newtype uses the instance of the underlying type (Show String, Show Int and Num Int).
{-# Language DerivingStrategies #-}
{-# Language GeneralizedNewtypeDeriving #-}
newtype CountryCode = CountryCode { cCode :: Int }
deriving
stock (Eq, Read)
deriving
newtype Num
newtype PlainString = PlainString String
deriving
newtype Show
newtype PhoneNo = PhoneNo { pNo :: Int }
deriving
stock (Eq, Read)
deriving
newtype (Num, Show)

Filter a list by returning only one kind of data constructor instead of the type of the type constructor

So let's say I have the following data type :
data CommandRequest = CreateWorkspace {commandId :: UUID , workspaceId ::UUID }
| IntroduceIdea {commandId :: UUID , workspaceId ::UUID , ideaContent :: String} deriving (Show,Eq)
with the {-# LANGUAGE DataKinds #-}
I want to implement the following function (in pseudocode) :
filter :: [CommandRequest] -> [CreateWorkspace] (promoting the data constructor to a type level)
can you help me with the implementation of that function ?... Thank you !
Given a Haskell type like:
data Foo = Bar Int | Baz String
there is no direct way of writing down a new type that represents the subset of values of type Foo that are constructed with Bar, even using the DataKinds extension.
In particular, when you turn on DataKinds, the Bar type that you get is not the type of the values Bar 1 and Bar 2. In fact, the new lifted Bar type doesn't really have anything to do with the values Bar 1 and Bar 2, except for the fact that they share the name Bar. It's not that different than explicitly defining:
data True = TrueThing
This new type True has nothing to do with the value True of type Bool, except they happen to have the same name.
Presuming that what you are trying to do is find a type-safe way of representing the result of filtering CommandRequest for just those values that were constructed with the CreateWorkspace constructor so that you can't "accidentally" let an IntroduceIdea sneak in to your list, you'll have to take another approach. There are several possibilities.
The most straightforward way, which doesn't require any special type-level programming at all, is to represent CreateWorkspace and IntroduceIdea as separate types:
{-# LANGUAGE DuplicateRecordFields #-}
data CreateWorkspace = CreateWorkspace
{ commandId :: UUID
, workspaceId ::UUID
} deriving (Show)
data IntroduceIdea = IntroduceIdea
{ commandId :: UUID
, workspaceId ::UUID
, ideaContent :: String
} deriving (Show)
and then create a new algebraic sum type to represent the disjoint union of those separate types:
data CommandRequest
= CreateWorkspace' CreateWorkspace
| IntroduceIdea' IntroduceIdea
deriving (Show)
Note we've used the ticks to differentiate these constructors from those used in the underlying component types. A simple variant of this would be to move common fields (like commandId, and perhaps workSpaceId) into the CommandRequest type. This might or might not make sense, depending on what you're trying to accomplish.
Anyway, this adds a little syntactic fluff, but it's straightforward to define:
filterCreateWorkspace :: [CommandRequest] -> [CreateWorkspace]
filterCreateWorkspace crs = [ cw | CreateWorkspace' cw <- crs ]
and with some additional "constructors":
createWorkspace :: UUID -> UUID -> CommandRequest
createWorkspace u1 u2 = CreateWorkspace' (CreateWorkspace u1 u2)
introduceIdea :: UUID -> UUID -> String -> CommandRequest
introduceIdea u1 u2 s = IntroduceIdea' (IntroduceIdea u1 u2 s)
it's not too hard to create and filter [CommandRequest] lists:
type UUID = Int
testdata1 :: [CommandRequest]
testdata1
= [ createWorkspace 1 2
, createWorkspace 3 4
, introduceIdea 5 6 "seven"
]
test1 = filterCreateWorkspace testdata1
giving:
> test1
[CreateWorkspace {commandId = 1, workspaceId = 2}
,CreateWorkspace {commandId = 3, workspaceId = 4}]
This is almost certainly the correct approach for doing what you want to do. I mean, this is exactly what algebraic data types are for. This is what a Haskell program is supposed to look like.
"But no," I hear you say! "I want to spend endless hours fighting confusing type errors! I want to crawl down the dependent type rabbit hole. You know, for 'reasons'." Should I stand in your way? Can one man stand against the ocean?
If you really want to do this at the type level, you still want to define separate types for your two constructors:
data CreateWorkspace = CreateWorkspace
{ commandId :: UUID
, workspaceId ::UUID
} deriving (Show)
data IntroduceIdea = IntroduceIdea
{ commandId :: UUID
, workspaceId ::UUID
, ideaContent :: String
} deriving (Show)
As before, this makes it easy to represent a list of type [CreateWorkspace]. Now, the key to working at the type level will be finding a way to make it as difficult as possible to represent a list of type [CommandRequest]. A standard method would be to introduce a CommandRequest type class with instances for our two types, together with an existential type to represent an arbitrary type belonging to that class:
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE ExistentialQuantification #-}
type UUID = Int -- for the sake of examples
data CreateWorkspace = CreateWorkspace
{ commandId :: UUID
, workspaceId ::UUID
} deriving (Show)
data IntroduceIdea = IntroduceIdea
{ commandId :: UUID
, workspaceId ::UUID
, ideaContent :: String
} deriving (Show)
class CommandRequest a where
maybeCreateWorkspace :: a -> Maybe CreateWorkspace
instance CommandRequest CreateWorkspace where
maybeCreateWorkspace c = Just c
instance CommandRequest IntroduceIdea where
maybeCreateWorkspace _ = Nothing
data SomeCommandRequest = forall t . CommandRequest t => SomeCommandRequest t
Now we can define:
import Data.Maybe
filterCreateWorkspace :: [SomeCommandRequest] -> [CreateWorkspace]
filterCreateWorkspace = catMaybes . map getCW
where getCW (SomeCommandRequest cr) = maybeCreateWorkspace cr
which works fine, though the syntax is still a bit cumbersome:
testdata2 :: [SomeCommandRequest]
testdata2 = [ SomeCommandRequest (CreateWorkspace 1 2)
, SomeCommandRequest (CreateWorkspace 3 4)
, SomeCommandRequest (IntroduceIdea 5 6 "seven")
]
test2 = print $ filterCreateWorkspace testdata2
The test gives:
> test2
[CreateWorkspace {commandId = 1, workspaceId = 2}
,CreateWorkspace {commandId = 3, workspaceId = 4}]
The awkward thing about this solution is that we need a type class method for identifying the CreateWorkspace type. If we wanted to construct lists of each possible constructor, we'd need to add a new type class method for every single one, and we need to give a definition for the method for every instance (though we can get away with a default definition that returns Nothing for all but one instance, I guess). Anyway, that's nuts!
The mistake we made was making it difficult to represent a list of type [CreateWorkspace] instead of absurdly difficult. To make it absurdly difficult, we'll still want to represent our two constructors as separate types, but we'll make them instances of a data family keyed by constructor names lifted to the type level by the DataKinds extension. Now this is starting to look like a Haskell program!
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE TypeFamilies #-}
data CommandRequestC = CreateWorkspace | IntroduceIdea
data family CommandRequest (c :: CommandRequestC)
type UUID = Int -- for the sake of examples
data instance CommandRequest CreateWorkspace
= CreateWorkspaceCR
{ commandId :: UUID
, workspaceId ::UUID
} deriving (Show)
data instance CommandRequest IntroduceIdea
= IntroduceIdeaCR
{ commandId :: UUID
, workspaceId ::UUID
, ideaContent :: String
} deriving (Show)
What's going on here? Well, we introduced a new type CommandRequestC (the trailing C stands for "constructor") with two constructors CreateWorkspace and IntroduceIdea. The only purpose of these constructors was to lift them to the type level using DataKinds in order to use them as type-level tags for the CommandRequest data family. This is a very common way of using DataKinds, maybe the most common. In fact, the example you gave of the type ReadResult 'RegularStream StreamSlice was exactly this kind of usage. The type:
data StreamType = All | RegularStream
carries no useful data. The whole point of its existence is to lift the constructors All and RegularStream to type-level tags, so that ReadResult 'All StreamSlice and ReadResult 'RegularStream StreamSlice can be used to name two different related types, just like CommandRequest 'CreateWorkspace and CommandRequest 'IntroduceIdea name two different related types.
At this point, we have two separate types for our two constructors that happen to be related via a tagged data family, rather than via a type class.
testdata3 :: [CommandRequest 'CreateWorkspace]
testdata3 = [CreateWorkspaceCR 1 2, CreateWorkspaceCR 3 4]
testdata4 :: [CommandRequest 'IntroduceIdea]
testdata4 = [IntroduceIdeaCR 5 6 "seven"]
Note that even though we can write the type [CommandRequest c], leaving the constructor tag as an unspecified type variable c, we still can't write a list that mixes these constructors:
testdata5bad :: [CommandRequest c]
testdata5bad = [CreateWorkspaceCR 1 2, CreateWorkspaceCR 3 4,
IntroduceIdeaCR 5 6 "seven"] -- **ERROR**
We still need our existential type:
{-# LANGUAGE ExistentialQuantification #-}
data SomeCommandRequest = forall c . SomeCommandRequest (CommandRequest c)
and the extra existential syntax:
testdata6 :: [SomeCommandRequest]
testdata6 = [ SomeCommandRequest (CreateWorkspaceCR 1 2)
, SomeCommandRequest (CreateWorkspaceCR 3 4)
, SomeCommandRequest (IntroduceIdeaCR 5 6 "seven")]
Worse yet, if we try to write a filter function, it's not clear how to implement it. One reasonable first attempt is:
filterCreateWorkspace :: [SomeCommandRequest] -> [CommandRequest 'CreateWorkspace]
filterCreateWorkspace (SomeCommandRequest cr : rest)
= case cr of cw#(CreateWorkspaceCR _ _) -> cw : filterCreateWorkspace rest
_ -> filterCreateWorkspace rest
but this fails with an error about failing to match to the CreateWorkspace tag.
The problem is that data families aren't powerful enough to allow you to infer which member of a family you actually have (i.e., whether cr is a CreateWorkspaceCR or IntroduceIdeaCR). At this point, we could go back to working with a type class or maybe introduce proxies or singletons to maintain a value-level representation of the constructors in the existential type, but there's a more straightforward solution.
GADTs are powerful enough to infer the type of cr, and we can rewrite our data family as a GADT. Not only is the syntax simpler:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE StandaloneDeriving #-}
data CommandRequestC = CreateWorkspace | IntroduceIdea
type UUID = Int
data CommandRequest c where
CreateWorkspaceCR :: UUID -> UUID -> CommandRequest 'CreateWorkspace
IntroduceIdeaCR :: UUID -> UUID -> String -> CommandRequest 'IntroduceIdea
deriving instance Show (CommandRequest c)
data SomeCommandRequest = forall c . SomeCommandRequest (CommandRequest c)
but we can implement our filtering function without fuss:
filterCreateWorkspace :: [SomeCommandRequest] -> [CommandRequest 'CreateWorkspace]
filterCreateWorkspace crs
= [ cw | SomeCommandRequest cw#(CreateWorkspaceCR _ _) <- crs ]
define some helpful "constructors":
createWorkspace :: UUID -> UUID -> SomeCommandRequest
createWorkspace u1 u2 = SomeCommandRequest (CreateWorkspaceCR u1 u2)
introduceIdea :: UUID -> UUID -> String -> SomeCommandRequest
introduceIdea u1 u2 s = SomeCommandRequest (IntroduceIdeaCR u1 u2 s)
and test it:
testdata7 :: [SomeCommandRequest]
testdata7 = [ createWorkspace 1 2
, createWorkspace 3 4
, introduceIdea 5 6 "seven"]
test7 = filterCreateWorkspace testdata7
like so:
> test4
[CreateWorkspaceCR 1 2,CreateWorkspaceCR 3 4]
>
Does any of this look familiar? It should, because it's #chi's solution. And it's the only type-level solution that really makes sense, giving what you're trying to do.
Now, with a couple of type aliases and some clever renaming, you can technically get the type signature you want, like so:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE StandaloneDeriving #-}
data CommandRequestC = CreateWorkspaceC | IntroduceIdeaC
type CreateWorkspace = ACommandRequest 'CreateWorkspaceC
type IntroduceIdea = ACommandRequest 'IntroduceIdeaC
type UUID = Int
data ACommandRequest c where
CreateWorkspaceCR :: UUID -> UUID -> CreateWorkspace
IntroduceIdeaCR :: UUID -> UUID -> String -> IntroduceIdea
deriving instance Show (ACommandRequest c)
data CommandRequest = forall c . CommandRequest (ACommandRequest c)
filterCreateWorkspace :: [CommandRequest] -> [CreateWorkspace]
filterCreateWorkspace crs
= [ cw | CommandRequest cw#(CreateWorkspaceCR _ _) <- crs ]
createWorkspace :: UUID -> UUID -> CommandRequest
createWorkspace u1 u2 = CommandRequest (CreateWorkspaceCR u1 u2)
introduceIdea :: UUID -> UUID -> String -> CommandRequest
introduceIdea u1 u2 s = CommandRequest (IntroduceIdeaCR u1 u2 s)
testdata8 :: [CommandRequest]
testdata8 = [ createWorkspace 1 2
, createWorkspace 3 4
, introduceIdea 5 6 "seven"]
test8 = filterCreateWorkspace testdata8
but this is just a trick, so I wouldn't take it too seriously.
And, if all this seems like a lot of work and leaves you feeling dissatisfied with the resulting solution, then welcome to the world of type-level programming! (Actually, it is all kind of fun, but try not to expect too much.)
You can use a list comprehension to filter only those values obtained through a specific constructor. Note that the type of the list does not change.
filter :: [CommandRequest] -> [CommandRequest]
filter xs = [ x | x#(CreateWorkspace{}) <- xs ]
If you want a more precise type, you need more complex type-level machinery, like GADTs.
Here's an untested approach. You'll need a few extensions to be turned on.
data CR = CW | II -- to be promoted to "kinds"
-- A more precise, indexed type
data CommandRequestP (k :: CR) where
CreateWorkspace :: {commandId :: UUID, workspaceId ::UUID }
-> CommandRequestP 'CW
IntroduceIdea :: {commandId :: UUID, workspaceId ::UUID, ideaContent :: String}
-> CommandRequestP 'II
-- Existential wrapper, so that we can build lists
data CommandRequest where
C :: CommandRequestP k -> CommandRequest
filter :: [CommandRequest] -> [CommandRequestP 'CW]
filter xs = [ x | C (x#(CreateWorkspace{})) <- xs ]

Name parametrized Data type

I am learning (once again) haskell and I have the following code:
import qualified Data.Map as Map
data Union = Union
{
father :: Int,
mother :: Int,
offspring :: [Int]
}
deriving Show
data Person = Person
{
uid :: Int,
name :: String,
parentUnion :: Maybe Union,
unions :: [Union]
}
deriving Show
family :: Map.Map Int Person
family = Map.fromList []
addPerson :: (Map.Map Int Person) -> Person -> (Map.Map Int Person)
addPerson family person
| Map.member puid family = error "Repeated id"
| otherwise = Map.insert puid person family
where puid = uid person
Now with more functions, I will have quite a lot of Map.Map Int Person types. Is there a way to define a type Family which is the same as Map.Map Int Person, so I could spell:
addPerson :: Family -> Person -> Family
My naive approach:
data Family = Map.Map Int Person
gives me a nice error:
Qualified constructor in data type declaration
Yes. To create "type synonyms" like that, just use type instead of data:
type Family = Map.Map Int Person
This makes Family exactly the same as writing out Map.Map Int Person. In fact, error messages will sometimes write out the full version instead of the synonym, so be ready for that.
Another option is to use a newtype:
newtype Family = Family (Map.Map Int Person)
The difference is that the newtype version is, like the name says, a new type: it is not directly compatible with Map.Map Int Person. If you try to use one where the other is expected, you will get an error. This is probably less useful for your example, but can be used to encode additional invariants in your types.
The newtype version is almost identical to a data construction:
data Family = Family (Map.Map Int Person)
It also introduces a new constructor Family which takes a single argument. However, unlike a normal data type, this only exists at compile time; the runtime representations of Family and Map.Map Int Person would still be exactly the same.

Basic Haskell: problems with a function

me again with another basic problem I have. I'm using ghci.
I (with help) created this working code:
newtype Name = Name String deriving (Show)
newtype Age = Age Int deriving (Show)
newtype Weight = Weight Int deriving (Show)
newtype Person = Person (Name, Age, Weight) deriving (Show)
isAdult :: Person -> Bool
isAdult (Person(_, Age a, _)) = a > 18
However problems occur when I tried making a more complex function updateWeight that allows the user to change a Person's weight from it's previous value. Can you point out where I have gone wrong?
updateWeight :: Person -> Int -> Person
updateWeight (Person(_,_,Weight w) b = (Person(_,_,w+b))
The problem is that you can't use the _ placeholder on the right hand side of an expression. You'll have to pass through the unchanged values. Also, you must wrap the result of w + b with a Weight again. This should work:
updateWeight :: Person -> Int -> Person
updateWeight (Person(n, a, Weight w) b = (Person(n, a, Weight (w + b)))
You can get rid of the boilerplate of passing through the unchanged values by using record syntax for the Person type.

How to "newtype" IntSet?

Thanks to newtype and the GeneralizedNewtypeDeriving extension, one can define distinct lightweight types with little effort:
newtype PersonId = PersonId Int deriving (Eq, Ord, Show, NFData, ...)
newtype GroupId = GroupId Int deriving (Eq, Ord, Show, NFData, ...)
which allows the type-system to make sure a PersonId is not used by accident where a GroupId was expected, but still inherit selected typeclass instances from Int.
Now one could simply define PersonIdSet and GroupIdSet as
import Data.Set (Set)
import qualified Data.Set as Set
type PersonIdSet = Set PersonId
type GroupIdSet = Set GroupId
noGroups :: GroupIdSet
noGroups = Set.empty
-- should not type-check
foo = PersonId 123 `Set.member` noGroups
-- should type-check
bar = GroupId 123 `Set.member` noGroups
which is type safe, since map is parametrized by the key-type, and also, the Set.member operation is polymorphic so I don't need to define per-id-type variants such as personIdSetMember and groupIdSetMember (and all other set-operations I might want to use)
...but how can I use the more efficient IntSets instead for PersonIdSet and GroupIdSet respectively in a similiar way to the example above? Is there a simple way w/o having to wrap/replicate the whole Data.IntSet API as a typeclass?
I think you have to wrap IntSet as you said. However, rather than defining each ID type separately, you can introduce a phantom type to create a family of IDs and IDSets that are compatible with one another:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import qualified Data.IntSet as IntSet
import Data.IntSet (IntSet)
newtype ID a = ID { unID :: Int }
deriving ( Eq, Ord, Show, Num )
newtype IDSet a = IDSet { unIDSet :: IntSet }
deriving ( Eq, Ord, Show )
null :: IDSet a -> Bool
null = IntSet.null . unIDSet
member :: ID a -> IDSet a -> Bool
member i = IntSet.member (unID i) . unIDSet
empty :: IDSet a
empty = IDSet $ IntSet.empty
singleton :: ID a -> IDSet a
singleton = IDSet . IntSet.singleton . unID
insert :: ID a -> IDSet a -> IDSet a
insert i = IDSet . IntSet.insert (unID i) . unIDSet
delete :: ID a -> IDSet a -> IDSet a
delete i = IDSet . IntSet.delete (unID i) . unIDSet
So, assuming you have a Person type, and a Group type, you can do:
type PersonID = ID Person
type PersonIDSet = IDSet Person
type GroupID = ID Group
type GroupIDSet = IDSet Group
The enummapset package implements one approach to newtype-safe IntMap/IntSets.
An example for its usage based on the types from the original question:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import Data.EnumSet (EnumSet)
import qualified Data.EnumSet as ES
newtype PersonId = PersonId Int deriving Enum
newtype GroupId = GroupId Int deriving Enum
type PersonIdSet = EnumSet PersonId
type GroupIdSet = EnumSet GroupId
noGroups :: GroupIdSet
noGroups = ES.empty
-- fails type-check: Couldn't match expected type `PersonId' with actual type `GroupId'
foo = PersonId 123 `ES.member` noGroups
-- passes type-check
bar = GroupId 123 `ES.member` noGroups
The usage of Data.EnumMap is similar.
I am under the impression you assume it is less efficient to use a type instead of a newtype. That is not true, newtypes are usually more efficiently implemented than datas.
So, your definition of PersonIdSet is perfectly safe and as efficient as you might want.

Resources