Episode 141: Daily dose of Haskell love!

4 views
Skip to first unread message

Hamish Mackenzie

unread,
May 24, 2016, 10:43:47 AM5/24/16
to illegal...@googlegroups.com
Thanks for another great podcast (as always).

I feel that languages with both generics and subtyping result in a confusing morass. Is this covariant or contraviant? Is it homogenous or heterogeneous? Will the type be inferred?

When I first started using Haskell I felt lost without subtyping and asked questions similar to the one that came up in the podcast. How does "a list of Show" work in Haskell?

As mentioned in the podcast you can define a function that takes a homogenous list of 'a' where 'a' can be shown:

showAll :: Show a => [a] -> String
showAll = concatMap show

Haskell magically transforms 'Show a' into a vtable like argument that it passes for you based on the compile time type. You can imagine the resulting code as something like this:

showAll' :: (a -> String) -> [a] -> String
showAll' s = concatMap s

Where the appropriate show function 's' is passed automatically based on the type of 'a'.

But what if some of the things in the list are different in some way? Perhaps 'show' needs to be implemented differently for some reason for some of them. One simple answer is to put the 'show' function in your list! (remember a list in an OO language typically contains pointers to vtables with functions in them):

customShowAll :: [(a -> String, a)] -> String
customShowAll = concatMap (\(s, a) -> s a)

Now you can have entries in your list with different show functions:

> customShowAll [(show, [1..10]), (show . take 5, [1..])]
"[1,2,3,4,5,6,7,8,9,10][1,2,3,4,5]"

BTW here is a great video about reflection in Haskell:
https://www.youtube.com/watch?v=asdABzBUoGM

Hamish


Mark Derricutt

unread,
May 24, 2016, 6:12:17 PM5/24/16
to illegal...@googlegroups.com

On 25 May 2016, at 2:43, Hamish Mackenzie wrote:

Thanks for another great podcast (as always).

We need to get you on sometime maybe :)

As mentioned in the podcast you can define a function that takes a homogenous list of 'a' where 'a' can be shown:

showAll :: Show a => [a] -> String
showAll = concatMap show

One of the things I was meaning to make clear, but seemed to skip and then the moment past was that the list here IS homogenous as you say, and not heterogeneous. So when used the invocation is still working on say a List Person, but enforced that there is an instance Show Person available, you wouldn't be able to pass in a Person and a Pet if they were disparate types.

Now you can have entries in your list with different show functions:

> customShowAll [(show, [1..10]), (show . take 5, [1..])]
"[1,2,3,4,5,6,7,8,9,10][1,2,3,4,5]"

Even here tho, a is locked to a concrete type. Unless they were all part of say:

data FamilyMember = Person { name :: String, age :: Int }
  | Pet { called :: String, breed :: String } 
  deriving Show

But then, the Show instance covers the cases.

Correct?

--
Mark Derricutt
http://www.theoryinpractice.net
http://www.chaliceofblood.net
http://plus.google.com/+MarkDerricutt
http://twitter.com/talios
http://facebook.com/mderricutt

signature.asc

Hamish Mackenzie

unread,
May 25, 2016, 4:42:55 AM5/25/16
to illegal...@googlegroups.com
> We need to get you on sometime maybe :)

I would be honoured.

> > customShowAll [(show, [1..10]), (show . take 5, [1..])]
> "[1,2,3,4,5,6,7,8,9,10][1,2,3,4,5]"
>
> Even here tho, a is locked to a concrete type. Unless they were all part of say:
>
> data FamilyMember = Person { name :: String, age :: Int }
> | Pet { called :: String, breed :: String }
> deriving Show
>
> But then, the Show instance covers the cases.
>
> Correct?

Well even then I think 'a' would still be concrete type (FamilyMember). You would use pattern matching to make a custom 'show' function that treated 'Person' and 'Pet' differently. Most of the time an algebraic data type like this will work nicely.

In a OO language you might simply override 'toString' to change the behaviour. I have included a maxLength parameter to make sure it needs to be a function in the Haskell code.

interface FamilyMember {
string toString(int maxLength) {...}
}

class Person : FamilyMember {
...
string toString(int maxLength) {...}
}

class Pet : FamilyMember {
...
string toString(int maxLength) {...}
}

This OO code is open in the sense that you can keep adding subtypes of FamilyMember without needing to modify the definition of FamilyMember.

If you need this sort of openness in Haskell then something like this will work:

{-# LANGUAGE RecordWildCards #-}
data FamilyMember = FamilyMember { showFamilyMember :: Int -> String }

data Person = Person { name :: String, age :: Int }
personToFamilyMember :: Person -> FamilyMember
personToFamilyMember Person{..} = FamilyMember { showFamilyMember = (`take` name) }

data Pet = Pet { called :: String, breed :: String }
petToFamilyMember :: Pet -> FamilyMember
petToFamilyMember Pet{..} = FamilyMember { showFamilyMember = (`take` called) }

main :: IO ()
main = mapM_ (putStrLn . (`showFamilyMember` 3)) [
personToFamilyMember (Person "Charlie" 68)
, petToFamilyMember (Pet “Snoopy” "Beagle") ]

Hamish
Reply all
Reply to author
Forward
0 new messages