Learning Purescript, some help defining types - purescript

I'm new to Functional Programming. I've used Ramda a bit (JavaScript library), but nothing like the type system in Purescript.
I have an idea that I feel should be expressible with Purescript's type system, but I'm not really sure where to start.
Lets say I'm trying to define some types for a Sudoku Board
newtype Index = Index Int
newtype Column = Column Int
newtype Row = Row Int
newtype Box = Box Int
I'd like to define what addition looks like for these types
In sudocode:
indexAddition :: (Index | Int) -> (Index | Int) -> Index
indexAddition a b = (a + b) % 81
RowAddition :: (Row | Int) -> (Row | Int) -> Row
RowAddition a b = (a + b) % 9
ColumnAddition and BoxAddition can probably me merged with RowAddition since they're gonna be basically the same.
-- I have to be able to say that a is a subset of Int, but Int isn't a type class
FooAddition :: forall a. Int a => a -> a -> a
FooAddition a b = (a + b) % 9
I somehow feel like I'm likely starting off on the wrong foot here.
Any help?

To answer your question directly, the way to have a function that works with different types, but a limited set of them (also known as "overloaded function") is type classes. More specifically, such function should be a method of a type class, and then you create an instance for each type (or combination of types) you'd like it to work with.
So the most straightforward approach would be this:
class IndexAddition a b where
indexAddition :: a -> b -> Index
instance addIntInt :: IndexAddition Int Int where
indexAddition a b = Index ((a+b) % 81)
instance addIntIndex :: IndexAddition Int Index where
indexAddition a (Index b) = Index ((a+b) % 81)
instance addIndexInt :: IndexAddition Index Int where
indexAddition (Index a) b = Index ((a+b) % 81)
instance addIndexIndex :: IndexAddition Index Index where
indexAddition (Index a) (Index b) = Index ((a+b) % 81)
As you can see, I made four instances, one for every combination of Index and Int. This works, but is admittedly a bit elaborate. Especially if you add a third parameter or a third possible type.
To make this a bit shorter and more manageable, you might observe that in order to add particular types, all you need from them is a way to convert to an Int. If you have that, you can convert both parameters to Int, then add, then wrap in Index:
class IsInt a where toInt :: a -> Int
instance ciIndex :: IsInt Index where toInt (Index a) = a
instance ciInt :: IsInt Int where toInt a = a
indexAddition :: forall a b. IsInt a => IsInt b => a -> b -> Index
indexAddition a b = Index ((toInt a + toInt b) % 81)
That said, I highly recommend that you reconsider your designs. Sure, ability to add numbers and indexes in any combination may look neat and nifty at first glance, but you probably will never need it in practice. And even if you do in some very specific circumstances, it's easy enough to just wrap/unwrap the values as needed. Trust me, I've been there. Many times.

Related

Mapping homogeneous record type

Suppose we have a record type that is homogeneous.
type RecI = { a :: Int, b :: Int, c :: Int, d :: Int, e :: Int }
We want to get from it type with the same keys but different value type:
type RecS = { a :: String, b :: String, c :: String, d :: String, e :: String }
Is it possible to get RecS type without explicitly defining all the keys from RecI?
And the second part of the question, what is the best way to implement mapping function from one type to another:
mapItoS :: (Int -> String) -> RecI -> RecS
?
To get a free-ish conversion from Int to String at type level, just give your record a parameter, then instantiate it with Int to get RecI and with String to get RecS:
type Rec a = { a :: a, b :: a, c :: a, d :: a, e :: a }
type RecI = Rec Int
type RecS = Rec String
To implement mapItoS, you can first convert to a Foreign.Object using fromHomogeneous, then map the function over it, then convert back to the record.
Unfortunately there is no toHomogeneous function, because in general you can't be sure that the Foreign.Object actually contains all required keys. But no matter: in this particular case you can be sure that it does, so you can get away with unsafeCoerce:
mapItoS :: forall a b. (a -> b) -> Rec a -> Rec b
mapItoS f = fromHomogeneous >>> map f >>> unsafeCoerce
A small self plug which is strictly relevant to the question :-P I've just published a library which provides many instances which allow a PureScripter to work with homogeneous Record and Variant:
https://pursuit.purescript.org/packages/purescript-homogeneous
I think it should have much better inference than solutions like heterogeneous. Please check it out and let me know what do you think.

Is unique morphism `m` which maps "best" product type to "suboptimal" product type truly unique?

I'm working through Bartosz Milewski's awesome blogs about category theory. I'm stuck on the one on products and coproducts.
Bartosz says that a product of two objects a and b is the object c equipped with two projections such that for any other object c' equipped with two projections there is a unique morphism m from c' to c that factorizes those projections.
Of course we can find a suitable example in the category of sets and functions. The product of two types Int and Bool is the pair (Int, Bool). The two projections are p (int, _) = int and q (_, bool) = bool. There is, however, another candidate for the product of Int and Bool: it is the type Int with projections p' int = int and q' _ = True. As Bartosz says: "That’s pretty lame, but it matches the criteria." Notice that the product type Int contains less values than the product type (Int, Bool). Half as much, to be precise. Even though both product types can be mapped to the entire Int type, the product type Int can be mapped to only half of the Bool type. It is not surjective (if that is the right word for it)!
Because we can come up with a mormphism m :: (Int, Bool) -> Int (where m can only be uniquely implemented as m (int, _) = int) we know that the product type (Int, Bool) is better than Int. At this point I wonder: couldn't we just as easily implement m as m (int, _) = int + 1? Isn't that a second morphism that works? Or is that not allowed because the resulting product Int is actually not Int at all, but Int "shifted" by 1?
L
The arrow in your m points in the wrong direction. We know that (Int, Bool) is "better" than Int because we have a morphism m :: Int -> (Int, Bool), m x = (x, True). It satisfies the conditions p . m == p' and q . m == q', that's what is meant by m factorizing p' and q'. If you define m in any other way, this condition will not hold.
For example, if it was defined as m x = (x + 1, True), then p (m 0) == 1, but p' 0 == 0.
Your map m' :: (Int, Bool) -> Int, if it factorized p and q, would point to Int being a suitable product, too. But the composition q' . m' :: (Int, Bool) -> Bool always returns True, so it cannot be equal to q.

Set of functions that are instances in a common way

I'm pretty new to haskell and I think I'm falling into some OO traps. Here's a sketch of a structure (simplified) that I'm having trouble implementing:
A concept of an Observable that acts on a list of samples (Int) to produce a result (Int)
A concept SimpleObservable that achieves the result using a certain pattern (while there will be Observables that do it other ways), e.g. something like an average
A function instance, e.g. one that's just an average times a constant
My first thought was to use a subclass; something like (the below is kinda contrived but hopefully gets the idea across)
class Observable a where
estimate :: a -> [Int] -> Int
class (Observable a) => SimpleObservable a where
compute :: a -> Int -> Int
simpleEstimate :: a -> [Int] -> Int
simpleEstimate obs list = sum $ map compute list
data AveConst = AveConst Int
instance Observable AveConst where
estimate = simpleEstimate
instance SimpleObservable AveConst where
compute (AveConst c) x = c * x
However, even if something like the above compiles it's ugly. Googling tells me DefaultSignatures might help in that I don't have to do estimate = simpleEstimate for each instance but from discussions around it it seems doing it this way would be an antipattern.
Another option would be to have no subclass, but something like (with the same Observable class):
data AveConst = AveConst Int
instance Observable AveConst where
estimate (AveConst c) list = sum $ map (*c) list
But this way I'm not sure how to reuse the pattern; each Observable has to contain the complete estimate definition and there will be code repetition.
A third way is a type with a function field:
data SimpleObservable = SimpleObservable {
compute :: [Int] -> Int
}
instance Observable SimpleObservable where
estimate obs list =
sum $ map (compute obs) list
aveConst :: Int -> SimpleObservable
aveConst c = SimpleObservable {
compute = (*c)
}
But I'm not sure this is idiomatic either. Any advice?
I propose going even simpler:
type Observable = [Int] -> Int
Then, an averaging observable is:
average :: Observable
average ns = sum ns `div` length ns
If your Observable needs some data inside -- say, a constant to multiply by -- no problem; that's what closures are for. For example:
sumTimesConst :: Int -> Observable
sumTimesConst c = sum . map (c*)
You can abstract over the construction of Observables without trouble; e.g. if you want a SimpleObservable which only looks at elements, and then sums, you can:
type SimpleObservable = Int -> Int
timesConst :: Int -> SimpleObservable
timesConst = (*)
liftSimple :: SimpleObservable -> Observable
liftSimple f = sum . map f
Then liftSimple . timesConst is another perfectly fine way to spell sumTimesConst.
...but honestly, I'd feel dirty doing any of the above things. sum . map (c*) is a perfectly readable expression without introducing a questionable new name for its type.
I do not fully understand the question yet but I'll edit this answer as I learn more.
Something which acts on a list and produces a result can simply be a function. The interface (that is, the type) of this function can be [a] -> b. This says the function accepts a list of elements of some type and returns a result of a possibly different type.
Now, lets invent a small problem as an example. I want to take a list of lists, some function on lists which produces a number, apply this function to every list, and return the average of the numbers.
average :: (Fractional b) => ([a] -> b) -> [[a]] -> b
average f xs = sum (fmap f xs) / genericLength xs
For example, average genericLength will tell me the average length of the sub-lists. I do not need to define any type classes or new types. Simply, I use the function type [a] -> b for those functions which map a list to some result.

Gentle Intro to Haskell: " .... there is no single type that contains both 2 and 'b'." Can I not make such a type ?

I am currently learning Haskell, so here are a beginner's questions:
What is meant by single type in the text below ?
Is single type a special Haskell term ? Does it mean atomic type here ?
Or does it mean that I can never make a list in Haskell in which I can put both 1 and 'c' ?
I was thinking that a type is a set of values.
So I cannot define a type that contains Chars and Ints ?
What about algebraic data types ?
Something like: data IntOrChar = In Int | Ch Char ? (I guess that should work but I am confused what the author meant by that sentence.)
Btw, is that the only way to make a list in Haskell in which I can put both Ints and Chars? Or is there a more tricky way ?
A Scala analogy: in Scala it would be possible to write implicit conversions to a type that represents both Ints and Chars (like IntOrChar) and then it would be possible to put seemlessly Ints and Chars into List[IntOrChar], is that not possible with Haskell ? Do I always have to explicitly wrap every Int or Char into IntOrChar if I want to put them into a list of IntOrChar ?
From Gentle Intro to Haskell:
Haskell also incorporates polymorphic types---types that are
universally quantified in some way over all types. Polymorphic type
expressions essentially describe families of types. For example,
(forall a)[a] is the family of types consisting of, for every type a,
the type of lists of a. Lists of integers (e.g. [1,2,3]), lists of
characters (['a','b','c']), even lists of lists of integers, etc., are
all members of this family. (Note, however, that [2,'b'] is not a
valid example, since there is no single type that contains both 2 and
'b'.)
Short answer.
In Haskell there are no implicit conversions. Also there are no union types - only disjoint unions(which are algebraic data types). So you can only write:
someList :: [IntOrChar]
someList = [In 1, Ch 'c']
Longer and certainly not gentle answer.
Note: This is a technique that's very rarely used. If you need it you're probably overcomplicating your API.
There are however existential types.
{-# LANGUAGE ExistentialQuantification, RankNTypes #-}
class IntOrChar a where
intOrChar :: a -> Either Int Char
instance IntOrChar Int where
intOrChar = Left
instance IntOrChar Char where
intOrChar = Right
data List = Nil
| forall a. (IntOrChar a) => Cons a List
someList :: List
someList = (1 :: Int) `Cons` ('c' `Cons` Nil)
Here I have created a typeclass IntOrChar with only function intOrChar. This way you can convert anything of type forall a. (IntOrChar a) => a to Either Int Char.
And also a special kind of list that uses existential type in its second constructor.
Here type variable a is bound(with forall) at the constructor scope. Therefore every time
you use Cons you can pass anything of type forall a. (IntOrChar a) => a as a first argument. Consequently during a destruction(i.e. pattern matching) the first argument will
still be forall a. (IntOrChar a) => a. The only thing you can do with it is either pass it on or call intOrChar on it and convert it to Either Int Char.
withHead :: (forall a. (IntOrChar a) => a -> b) -> List -> Maybe b
withHead f Nil = Nothing
withHead f (Cons x _) = Just (f x)
intOrCharToString :: (IntOrChar a) => a -> String
intOrCharToString x =
case intOrChar of
Left i -> show i
Right c -> show c
someListHeadString :: Maybe String
someListHeadString = withHead intOrCharToString someList
Again note that you cannot write
{- Wont compile
safeHead :: IntOrChar a => List -> Maybe a
safeHead Nil = Nothing
safeHead (Cons x _) = Just x
-}
-- This will
safeHead2 :: List -> Maybe (Either Int Char)
safeHead2 Nil = Nothing
safeHead2 (Cons x _) = Just (intOrChar x)
safeHead will not work because you want a type of IntOrChar a => Maybe a with a bound at safeHead scope and Just x will have a type of IntOrChar a1 => Maybe a1 with a1 bound at Cons scope.
In Scala there are types that include both Int and Char such as AnyVal and Any, which are both supertypes of Char and Int. In Haskell there is no such hierarchy, and all the basic types are disjoint.
You can create your own union types which describe the concept of 'either an Int or a Char (or you could use the built-in Either type), but there are no implicit conversions in Haskell to transparently convert an Int into an IntOrChar.
You could emulate the concept of 'Any' using existential types:
data AnyBox = forall a. (Show a, Hashable a) => AB a
heteroList :: [AnyBox]
heteroList = [AB (1::Int), AB 'b']
showWithHash :: AnyBox -> String
showWithHash (AB v) = show v ++ " - " ++ (show . hash) v
let strs = map showWithHash heteroList
Be aware that this pattern is discouraged however.
I think that the distinction that is being made here is that your algebraic data type IntOrChar is a "tagged union" - that is, when you have a value of type IntOrChar you will know if it is an Int or a Char.
By comparison consider this anonymous union definition (in C):
typedef union { char c; int i; } intorchar;
If you are given a value of type intorchar you don't know (apriori) which selector is valid. That's why most of the time the union constructor is used in conjunction with a struct to form a tagged-union construction:
typedef struct {
int tag;
union { char c; int i; } intorchar_u
} IntOrChar;
Here the tag field encodes which selector of the union is valid.
The other major use of the union constructor is to overlay two structures to get an efficient mapping between sub-structures. For example, this union is one way to efficiently access the individual bytes of a int (assuming 8-bit chars and 32-bit ints):
union { char b[4]; int i }
Now, to illustrate the main difference between "tagged unions" and "anonymous unions" consider how you go about defining a function on these types.
To define a function on an IntOrChar value (the tagged union) I claim you need to supply two functions - one which takes an Int (in the case that the value is an Int) and one which takes a Char (in case the value is a Char). Since the value is tagged with its type, it knows which of the two functions it should use.
If we let F(a,b) denote the set of functions from type a to type b, we have:
F(IntOrChar,b) = F(Int,b) \times F(Char,b)
where \times denotes the cross product.
As for the anonymous union intorchar, since a value doesn't encode anything bout its type the only functions which can be applied are those which are valid for both Int and Char values, i.e.:
F(intorchar,b) = F(Int,b) \cap F(Char,b)
where \cap denotes intersection.
In Haskell there is only one function (to my knowledge) which can be applied to both integers and chars, namely the identity function. So there's not much you could do with a list like [2, 'b'] in Haskell. In other languages this intersection may not be empty, and then constructions like this make more sense.
To summarize, you can have integers and characters in the same list if you create a tagged-union, and in that case you have to tag each of the values which will make you list look like:
[ I 2, C 'b', ... ]
If you don't tag your values then you are creating something akin to an anonymous union, but since there aren't any (useful) functions which can be applied to both integers and chars there's not really anything you can do with that kind of union.

haskell class property check

I would like to have a function that checks a property of a class instance:
class ListWithAtLeastOneElement a where
list :: [a]
check :: Bool
check = (length list) >= 1
but I get this error when compiling in GHC:
"The class method 'check' mentions none of the type variables of the class ListWithAtLeastOneElement a When checking the class method: check :: Bool In the class declaration for 'ListWithAtLeastOneElement'"
Is there a better way of doing what I want, or a way to get this to compile in GHC?
You seem to think a class in Haskell is like a class in an OO-language. That is not true. You should use a data type or newtype or type synonym.
newtype NonemptyList a = List1 [a]
fromList :: [a] -> Maybe (NonemptyList a)
fromList [] = Nothing
fromList xs#(_:_) = Just $ List1 xs
check :: NonemptyList a -> Bool
check (List1 xs) = length xs >= 1
As jmg said, this isn't valid Haskell so it's good GHC isn't compiling it! If you're used to Java evidently you should think of Haskell's type classes like Java interface. If that doesn't help then perhaps you should read LYAH's chapter on classes.
For you problem, it appears you're wanting a list-like data type that can never be null. You don't need to test for such a property, you can statically ensure it by using a data
type that can never be empty:
-- Notice this data type can never have zero 'a' values!
data NonEmptyList a = NEL a (NonEmptyList a) | Singleton a
-- We can define basic operators for this, just like list has
-- You can't pattern match with them, but there are work-arounds for that if you want to ask
(.:) = NEL -- concatenate non-empty lists
nelHead :: NonEmptyList a -> a
nelHead (NEL a _) = a
nelHead (Singleton a) = a
nelTail :: NonEmptyList a -> Maybe (NonEmptyList a)
nelTail (NEL _ b) = Just b
nelTail _ = Nothing
nelTake :: Int -> NonEmptyList a -> NonEmptyList a
nelTake 1 (NEL a _) = Singleton a
nelTake 1 (Singleton a) = Singleton a
nelTake n (NEL a rest) = a .: nelTake (n-1) rest
nelDrop :: Int -> NonEmptyList a -> NonEmptyList a
nelDrop _ (Singleton _) = error "Perhaps I should have used the 'maybe' type"
nelDrop 1 (NEL a r) = r
nelDrop n (NEL a r) = nelDrop (n-1) r
And so on and so forth. It's worth noting nelTake and nelDrop are partial but nelHead is total, funny as this is the opposite of regular lists.
You would need to make check a function
check :: [a] -> Bool
That said you are better off with a data type for non-empty lists rather than a class, classes in Haskell do not serve the same purpose as classes in object oriented languages.