Exercises part 3
These exercises are meant to make you comfortable with implementing interfaces for your own data types, as you will have to do so regularly when writing Idris code.
While it is immediately clear why interfaces like Eq, Ord, or Num are useful, the usability of Semigroup and Monoid may be harder to appreciate at first. Therefore, there are several exercises where you'll implement different instances for these.
-
Define a record type
Complexfor complex numbers, by pairing two values of typeDouble. Implement interfacesEq,Num,Neg, andFractionalforComplex. -
Implement interface
ShowforComplex. Have a look at data typePrecand functionshowPrecand how these are used in the Prelude to implement instances forEitherandMaybe.Verify the correct behavior of your implementation by wrapping a value of type
Complexin aJustandshowthe result at the REPL. -
Consider the following wrapper for optional values:
record First a where constructor MkFirst value : Maybe aImplement interfaces
Eq,Ord,Show,FromString,FromChar,FromDouble,Num,Neg,Integral, andFractionalforFirst a. All of these will require corresponding constraints on type parametera. Consider implementing and using the following utility functions where they make sense:pureFirst : a -> First a mapFirst : (a -> b) -> First a -> First b mapFirst2 : (a -> b -> c) -> First a -> First b -> First c -
Implement interfaces
SemigroupandMonoidforFirst ain such a way, that(<+>)will return the first non-nothing argument andneutralis the corresponding neutral element. There must be no constraints on type parameterain these implementations. -
Repeat exercises 3 and 4 for record
Last. TheSemigroupimplementation should return the last non-nothing value.record Last a where constructor MkLast value : Maybe a -
Function
foldMapallows us to map a function returning aMonoidover a list of values and accumulate the result using(<+>)at the same time. This is a very powerful way to accumulate the values stored in a list. UsefoldMapandLastto extract the last element (if any) from a list.Note, that the type of
foldMapis more general and not specialized to lists only. It works also forMaybe,Eitherand other container types we haven't looked at so far. We will learn about interfaceFoldablein a later section. -
Consider record wrappers
AnyandAllfor boolean values:record Any where constructor MkAny any : Bool record All where constructor MkAll all : BoolImplement
SemigroupandMonoidforAny, so that the result of(<+>)isTrue, if and only if at least one of the arguments isTrue. Make sure thatneutralis indeed the neutral element for this operation.Likewise, implement
SemigroupandMonoidforAll, so that the result of(<+>)isTrue, if and only if both of the arguments areTrue. Make sure thatneutralis indeed the neutral element for this operation. -
Implement functions
anyElemandallElemsusingfoldMapandAnyorAll, respectively:-- True, if the predicate holds for at least one element anyElem : (a -> Bool) -> List a -> Bool -- True, if the predicate holds for all elements allElems : (a -> Bool) -> List a -> Bool -
Record wrappers
SumandProductare mainly used to hold numeric types.record Sum a where constructor MkSum value : a record Product a where constructor MkProduct value : aGiven an implementation of
Num a, implementSemigroup (Sum a)andMonoid (Sum a), so that(<+>)corresponds to addition.Likewise, implement
Semigroup (Product a)andMonoid (Product a), so that(<+>)corresponds to multiplication.When implementing
neutral, remember that you can use integer literals when working with numeric types. -
Implement
sumListandproductListby usingfoldMaptogether with the wrappers from Exercise 9:sumList : Num a => List a -> a productList : Num a => List a -> a -
To appreciate the power and versatility of
foldMap, after solving exercises 6 to 10 (or by loadingSolutions.Inderfacesin a REPL session), run the following at the REPL, which will - in a single list traversal! - calculate the first and last element of the list as well as the sum and product of all values.> foldMap (\x => (pureFirst x, pureLast x, MkSum x, MkProduct x)) [3,7,4,12] (MkFirst (Just 3), (MkLast (Just 12), (MkSum 26, MkProduct 1008)))Note, that there are also
Semigroupimplementations for types with anOrdimplementation, which will return the smaller or larger of two values. In case of types with an absolute minimum or maximum (for instance, 0 for natural numbers, or 0 and 255 forBits8), these can even be extended toMonoid. -
In an earlier exercise, you implemented a data type representing chemical elements and wrote a function for calculating their atomic masses. Define a new single field record type for representing atomic masses, and implement interfaces
Eq,Ord,Show,FromDouble,Semigroup, andMonoidfor this. -
Use the new data type from exercise 12 to calculate the atomic mass of an element and compute the molecular mass of a molecule given by its formula.
Hint: With a suitable utility function, you can use
foldMaponce again for this.
Final notes: If you are new to functional programming, make sure to give your implementations of exercises 6 to 10 a try at the REPL. Note, how we can implement all of these functions with a minimal amount of code and how, as shown in exercise 11, these behaviors can be combined in a single list traversal.