Doubting Haskell

View: New views
20 Messages — Rating Filter:   Alert me  
< Prev | 1 - 2 - 3 | Next >

Re: Re: Doubting Haskell

by Colin Paul Adams :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

>>>>> "apfelmus" == apfelmus  <apfelmus@...> writes:

    apfelmus> Colin Paul Adams wrote:
    >> Left? Right?
    >>
    >> Hardly descriptive terms. Sounds like a sinister language to
    >> me.

    apfelmus> The mnemonics is that Right x is "right" in the sense of
    apfelmus> correct. So, the error case has to be Left err .

As I said, this is sinister (i.e. regarding left-handed people as
evil).

And left is not the opposite of correct. That would be incorrect.

Also, it is not clear to me that a failure to read a file (for
instance) is incorrect behaviour. If the file doesn't exist, then I
think it ought to be considered correct behaviour to fail to read the
file.

So Success and Failure seem to be much better. Certainly they make the
program far more readable to my eyes.
--
Colin Adams
Preston Lancashire
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@...
http://www.haskell.org/mailman/listinfo/haskell-cafe

Re: Re: Doubting Haskell

by Jonathan Cast-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


On 17 Feb 2008, at 1:12 AM, Colin Paul Adams wrote:

>>>>>> "apfelmus" == apfelmus  <apfelmus@...> writes:
>
>     apfelmus> Colin Paul Adams wrote:
>>> Left? Right?
>>>
>>> Hardly descriptive terms. Sounds like a sinister language to
>>> me.
>
>     apfelmus> The mnemonics is that Right x is "right" in the sense of
>     apfelmus> correct. So, the error case has to be Left err .
>
> As I said, this is sinister

You do know what `sinister' means, no?

> (i.e. regarding left-handed people as
> evil).

Sheesh, it's just a mnemonic...

>
> And left is not the opposite of correct. That would be incorrect.

No, Left is the opposite of Right.  Right is the constructor modified  
by fmap (due to the design of Haskell type classes); therefore return  
= Right.  Therefore any computation in Either that is not the result  
of a return is an application of Left.

> Also, it is not clear to me that a failure to read a file (for
> instance) is incorrect behaviour.

Then don't think of Left as `incorrect behavior'.  Left isn't  
incorrect, or Parsec's parse function wouldn't return it on parse  
errors.

> If the file doesn't exist, then I
> think it ought to be considered correct behaviour to fail to read the
> file.
>
> So Success and Failure seem to be much better. Certainly they make the
> program far more readable to my eyes.

But the program succeeded in doing what I expected it to do when if  
failed...

jcc

Besides, these decisions were made 15 years ago, they're not going to  
change now...

_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@...
http://www.haskell.org/mailman/listinfo/haskell-cafe

Re: Doubting Haskell

by Heinrich Apfelmus :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Alan Carter wrote:
> We now need to be able to do parallel with ease. Functional
> programming just got really important.

While this is a reason to have a look at Haskell, I think it's not the
best one. In fact, I think it's probably harmful to have parallelism as
single goal for learning Haskell: the language is very different from
imperative languages and if you "just want to do parallelism" but
otherwise stick to what you know, you'll have a really hard time.

> Eventually a 3 page
> introduction on the O'Reilly website together with a good document
> called "Haskell for C Programmers" got me to the point where I could
> access "Yet Another Haskell Tutorial", and I was away... for a bit.

IMHO, the easiest way to learn Haskell and to appreciate functional
programming is to learn it from a textbook :) The online tutorials are
nice but you'll have a much harder time with them.

I'd recommend Hutton's "Programming in Haskell" for the basics and
Bird's "Introduction to Functional Programming using Haskell" for the
functional style. See also

   http://haskell.org/haskellwiki/Books#Textbooks


Of course, the textbooks (except "Real World Haskll" which is not done
yet) most likely don't cover the System.IO stuff, but you've got the
#haskell irc channel and the mailing list for that. So, the textbook
remark is in anticipation of the questions that you are going to have :)
(if you decide to pursue Haskell further, that is).


Regards,
apfelmus

_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@...
http://www.haskell.org/mailman/listinfo/haskell-cafe

Re: Doubting Haskell

by Thomas Schilling-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


On 17 feb 2008, at 08.46, Anton van Straaten wrote:

> Colin Paul Adams wrote:
>>>>>>> "Cale" == Cale Gibbard <cgibbard@...> writes:
>>     Cale> So, the first version:
>>     Cale> import System.IO import Control.Exception (try)
>>     Cale> main = do mfh <- try (openFile "myFile" ReadMode) case mfh
>>     Cale> of Left err -> do putStr "Error opening file for reading: "
>>     Cale> print err Right fh -> do mline <- try (hGetLine fh) case
>>     Cale> mline of Left err -> do putStr "Error reading line: " print
>>     Cale> err hClose fh Right line -> putStrLn ("Read: " ++ line)
>> Left? Right?
>> Hardly descriptive terms. Sounds like a sinister language to me.
>
> I was thinking along the same lines.  Politically-sensitive left-
> handed people everywhere ought to be offended that "Left" is the  
> alternative used to represent errors, mnemonic value notwithstanding.
>
> Is there a benefit to reusing a generic Either type for this sort  
> of thing?  For code comprehensibility, wouldn't it be better to use  
> more specific names?  If I want car and cdr, I know where to find it.

Haskell doesn't have constructor aliases and keeping around dozens of  
isomorphic types would be stupid.  (Views could help, though.)

Also, "Right" is naturally used when the everything was alright.  It  
might be arbitrary, but it's not hard to remember - once you're past  
the newbie phase no-one confuses car and cdr anyways...
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@...
http://www.haskell.org/mailman/listinfo/haskell-cafe

Re: Doubting Haskell

by Philippa Cowderoy :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Sun, 17 Feb 2008, Anton van Straaten wrote:

> Is there a benefit to reusing a generic Either type for this sort of thing?
> For code comprehensibility, wouldn't it be better to use more specific
> names?  If I want car and cdr, I know where to find it.
>

It's Haskell's standard sum type, with a pile of instances already
written. There's an instance of MonadError such that you only need to see
an Either when you run the computation for example (and then you get an
Either whatever the actual error monad was!). If we had appropriate
language extensions to map an isomorphic Success/Failure type onto it then
I'd probably use them - as it is, the level of inertia around Either is
great enough to mean that's only worth doing if I'm expecting to roll a
third constructor in at some point.

That said, generally I'll wrap it up pretty fast if I have to handle
Either directly. Not that that's necessarily any different to cons, car
and cdr of course, but there's plenty of library support for doing so.

--
flippa@...

"I think you mean Philippa. I believe Phillipa is the one from an
alternate universe, who has a beard and programs in BASIC, using only
gotos for control flow." -- Anton van Straaten on Lambda the Ultimate
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@...
http://www.haskell.org/mailman/listinfo/haskell-cafe

Re: Re: Doubting Haskell

by Donn Cave-4 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Feb 17, 2008, at 1:12 AM, Colin Paul Adams wrote:

>
> And left is not the opposite of correct. That would be incorrect.
>
> Also, it is not clear to me that a failure to read a file (for
> instance) is incorrect behaviour. If the file doesn't exist, then I
> think it ought to be considered correct behaviour to fail to read the
> file.

Well, of course correct behavior is to cope with both cases in
the most appropriate way.

If it's any consolation to those of the left handed persuasion, I  
guessed
it wrong - I have used Either in this way, but Left was Success and
Right was Failure.  I don't enjoy puns, and mapped to an A/B form
it seemed obvious that Success is A.

        Donn Cave, donn@...
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@...
http://www.haskell.org/mailman/listinfo/haskell-cafe

Re: Doubting Haskell

by jerzy.karczmarczuk :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Donn Cave writes:

> On Feb 17, 2008, at 1:12 AM, Colin Paul Adams wrote:

>> And left is not the opposite of correct. That would be incorrect.
...
> If it's any consolation to those of the left handed persuasion, I  guessed
> it wrong - I have used Either in this way, but Left was Success and
> Right was Failure.  I don't enjoy puns, and mapped to an A/B form
> it seemed obvious that Success is A.

Weellll, for those who don't enjoy puns, but feel that the life and
everything is one enormous pun, a political reminder.

For many years, the world is composed of Leftists and Rightists (let's
for the moment forget the normal people). Those from the Left always felt
that they were right, and that those from the Right should not be left
unpunished, while those from the Right thought that those from the Left
should be left to die. Even if it seems right to consider that these
deviations should be left to historians, we should not forget that at
the beginning of the glorious Soviet country there was a proposal to
change the meaning of traffic lights. Red would mean "Forward!!".

On the other hand, in France nowadays the difference between Right and
Left is more or less the same as between "Immediate failure" and "Delayed
failure". Choose yourselves which is which.

Jerzy Karczmarczuk


_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@...
http://www.haskell.org/mailman/listinfo/haskell-cafe

Re: Doubting Haskell

by Mads Lindstrøm :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Hi Alan

I can help but feeling curious. Did some of the answers actually help
you? Are you still as doubtful about Haskell as when you wrote your
email?


Greetings,

Mads Lindstrøm




_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@...
http://www.haskell.org/mailman/listinfo/haskell-cafe

Re: Doubting Haskell

by John Meacham :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Sat, Feb 16, 2008 at 05:04:53PM -0800, Donn Cave wrote:
> But in Haskell, you cannot read a file line by line without writing an
> exception handler, because end of file is an exception!  as if a file does
> not normally have an end where the authors of these library functions
> came from?

Part of it is that using 'getLine' is not idiomatic haskell when you
don't want to worry about exceptions. Generally you do something like

doMyThing xs = print (length xs)

main = do
        contents <-  readFile "my.file"
        mapM_ doMyThing (lines contents)


which will call 'doMyThing' on each line of the file, in this case
printing the length of each line.

or more succinctly:

main = readFile "my.file" >>= mapM_ doMyThing . lines


        John

--
John Meacham - ⑆repetae.net⑆john⑈
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@...
http://www.haskell.org/mailman/listinfo/haskell-cafe

Re: Doubting Haskell

by Simon Marlow-5 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Bryan O'Sullivan wrote:
> Stefan O'Rear wrote:
>
>> I'll bet that breaks horribly in the not-so-corner case of /dev/tty.
>
> Actually, it doesn't.  It seems to do a read behind the scenes if the
> buffer is empty, so it blocks until you type something.

Indeed, and this is why even an unbuffered Handle needs to have a
1-character buffer, an interesting fact I discovered when implementing the
I/O library.

Cheers,
        Simon
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@...
http://www.haskell.org/mailman/listinfo/haskell-cafe

Re: Re: Doubting Haskell

by Wolfgang Jeltsch-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Am Sonntag, 17. Februar 2008 10:12 schrieb Colin Paul Adams:
> The mnemonics is that Right x is "right" in the sense of
> correct. So, the error case has to be Left err .
>
> As I said, this is sinister (i.e. regarding left-handed people as
> evil).

I hardly can believe that you mean this seriously.  Do you really think that
the Haskell architects wanted to offend left-handed people?  What does assure
you that the names of the Either constructors are about handedness?  Are you
really so sensitive that you want to make people think about all kinds of
misinterpretations the usage of an everyday word may cause before they use
it?  I’d propose that people don’t search for non-existent defamation so that
productivity doesn’t get buried under the search for “politically correct”
words.

Actually, I wouldn’t have dreamed of Left being related to left-handedness.  
To me, it has long been very clear that Left and Right were assigned its
meaning this way round because otherwise you wouldn’t get Functor and Monad
instances.  A pure technical reason, having nothing to do with hands,
politics and whatever you might think of.

> […]

Best wishes,
Wolfgang
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@...
http://www.haskell.org/mailman/listinfo/haskell-cafe

Parent Message unknown Re: Doubting Haskell

by Cale Gibbard :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

(I'm copying the list on this, since my reply contains a tutorial
which might be of use to other beginners.)

On 19/02/2008, Alan Carter <alangcarter@...> wrote:

> Hi Cale,
>
> On Feb 19, 2008 3:48 PM, Cale Gibbard <cgibbard@...> wrote:
> > Just checking up, since you haven't replied on the list. Was my
> > information useful? Did I miss any questions you might have had? If
> > you'd like, I posted some examples of using catch here:
>
> Thanks for your enquiry! My experiment continues. I did put a progress
> report on the list - your examples together with a similar long an
> short pair got me over the file opening problem, and taught me some
> things about active whitespace :-) I couldn't get withFile working
> (says out of scope, maybe 'cos I'm ghc 6.6 on my Mac)

Make sure to put:

import System.IO

at the top of your source file, if you haven't been. This should
import everything documented here:
http://www.haskell.org/ghc/docs/latest/html/libraries/base/System-IO.html

> but it turned out the line I was looking for (collapsed from the examples)
> was:
>
>   text <- readFile "data.txt" `catch` \_ -> return ""
>
> This ensures the program never loses control, crashing or becoming
> unpredictable by attempting to use an invalid resource, by yielding an
> empty String if for any reason the file read fails. Then an empty
> String makes it very quickly through parsing. I guess that's quite
> "functiony" :-)
>
> Amazing how easy once I knew how. Even stranger that I couldn't find a
> "bread and butter" example of it.
>
> Then I was going very quickly for a while. My file is dumped from a
> WordPress MySql table. Well formed lines have 4 tab separated fields
> (I'm using pipes for tabs here):
>
> line id | record id | property | value
>
> Line IDs are unique and don't matter. All lines with the same record
> ID give a value to a property in the same record, similar to this:
>
> 1|1|name|arthur
> 2|1|quest|seek holy grail
> 3|1|colour|blue
> 4|2|name|robin
> 5|2|quest|run away
> 6|2|colour|yellow
>
> Organizing that was a joy. It took minutes:

let cutUp = tail (filter (\fields -> (length fields) == 4)
                                      (map (\x -> split x '\t') (lines text)))

This should almost certainly be a function of text:

cutUp text = tail (filter (\fields -> (length fields) == 4)
                                 (map (\x -> split x '\t') (lines text)))

> I found a split on someone's blog (looking for a library tokenizer),
> but I can understand it just fine. I even get to chuck out ill-formed
> lines and remove the very first (which contains MySql column names) on
> the way through!

Sadly, there's no general library function for doing this. We have
words and lines (and words would work here, if your fields never have
spaces), but nobody's bothered to put anything more general for simple
splitting into the base libraries (though I'm sure there's plenty on
hackage -- MissingH has a Data.String.Utils module which contains
split and a bunch of others, for example). However, for anything more
complicated, there are also libraries like Parsec, which are generally
really effective, so I highly recommend looking at that at some point.

> I then made a record to put things in, and wrote some lines to play
> with it (these are the real property names):
>
> data Entry = Entry
>   { occupation                :: String
>   , iEnjoyMyJob               :: Int
>   , myJobIsWellDefined        :: Int
>   , myCoworkersAreCooperative :: Int
>   , myWorkplaceIsStressful    :: Int
>   , myJobIsStressful          :: Int
>   , moraleIsGoodWhereIWork    :: Int
>   , iGetFrustratedAtWork      :: Int
>   }
> ...
>   let e = Entry{occupation = "", iEnjoyMyJob = 0}
>   let f = e {occupation = "alan"}
>   let g = f {iEnjoyMyJob = 47}
>   putStrLn ((occupation g) ++ " " ++ (show (iEnjoyMyJob g)))
>
> Then I ran into another quagmire. I think I have to use Data.Map to
> build a collection of records keyed by record id, and fill them in by
> working through the list of 4 item lists called cutUp. As with the
> file opening problem I can find a few examples that convert a list of
> tuples to a Data.Map, one to one. I found a very complex example that
> convinced me a map from Int to a record is possible, but gave me no
> understanding of how to do it. I spent a while trying to use foldl
> before I decided it can't be appropriate (I need to pass more values).
> So I tried a couple of recursive functions, something like:
>
> type Entries = M.Map Int Entry
> ...
>   let entries = loadEntries cutUp
> ...
> loadEntries :: [[String]] -> Entries
> loadEntries [] = M.empty Entries
> loadEntries [x : xs] = loadEntry (loadEntries xs) x
-- Possible common beginner error here: [x:xs] means the list with one
element which is a list whose first element is x and whose tail is xs.
Your type signature and the type of cutUp seems to confirm that this
is the right type, but you don't seem to have a case to handle a
longer list of lists. If you want just a list with first entry x, and
with tail xs, that's just (x:xs). If you want to handle lists of lists
recursively, you'll generally need two cases: ([]:xss) and
((x:xs):xss). We'll end up doing something different instead of
recursion in a moment.

>
> loadEntry entries _ rid fld val = entries
>
> Trying to create an empty map at the bottom of the recursion so later
> I can try to fiddle about checking if the key is present and crating a
> new record otherwise, then updating the record with a changed one (a
> big case would be needed deep in to do each property update). If I'm
> on the right track it's not good enough to get better, so now I'm just
> throwing bits of forest animals into the pot at random again :-(
>
> So I certainly would be grateful for a clue! The bits I can do (I got
> a non-trivial wxHaskell frame sorted out quite easily, the tokenizing
> and record bit were OK) I think show I'm not *totally* stupid at this,
> I'm putting loads of time investment in (it's an experiement in
> itself) but there do seem to be certain specific things that would be
> ubiquitous patterns in any production or scripting environment, which
> are not discussed at all and far from obvious. The more I see of
> Haskell the more I suspect this issue is the gating one for popular
> uptake.
>
> I couldn't help thinking of this bit, from the Wikipedia entry on the
> Cocteau Twins:
>
> "The band's seventh LP, Four-Calendar Café, was released in late 1993.
> It was a departure from the heavily-processed, complex and layered
> sounds of Blue Bell Knoll and Heaven or Las Vegas, featuring clearer
> and more minimalistic arrangements. This, along with the record's
> unusually comprehensible lyrics, led to mixed reviews for the album:
> Some critics accused the group of selling out and producing an
> 'accessible album,' while others praised the new direction as a
> felicitous development worthy of comparison with Heaven or Las Vegas."
>
> Best wishes,
>
> Alan

I woke up rather early, and haven't much to do, so I'll turn this into
a tutorial. :)

Okay. The most common ways to build a map are by using the fromList,
fromListWith, or fromListWithKey functions. You can see them in the
documentation here:

http://www.haskell.org/ghc/docs/latest/html/libraries/containers/Data-Map.html#v%3AfromList

The types are:

fromList :: (Ord k) => [(k,a)] -> Map k a

fromListWith :: (Ord k) => (a -> a -> a) -> [(k,a)] -> Map k a

fromListWithKey :: (Ord k) => (k -> a -> a -> a) -> [(k,a)] -> Map k a

They take a list of (key,value) pairs, and build a map from it.
Additionally, the fromListWith function takes a function which
specifies how the values should be combined if their keys collide.
There is also a fromListWithKey function which allows the means of
combination to depend on the key as well.

At this point we realise something interesting about the way the data
is being represented: if there is a field in someone's record with no
row in the database, what should the resulting field contain? In C,
they often use some integer which is out of range, like -1 for this.

How about for a missing occupation field? Well, that's a String, you
could use some generic failure string, or an empty string, but I'll
show you another possibility that just might be convenient.

If t is any type, then the type (Maybe t) consists of the values
Nothing, and Just x, whenever x is a value of type t. This is another
convenient way to represent the idea that a computation might fail.

Let's start by changing your record type so that each field is a Maybe
value, that is, either the value Nothing, or the value Just x, where x
is the value it would have been.

data Entry = Entry
  { occupation                :: Maybe String
  , iEnjoyMyJob               :: Maybe Int
  , myJobIsWellDefined        :: Maybe Int
  , myCoworkersAreCooperative :: Maybe Int
  , myWorkplaceIsStressful    :: Maybe Int
  , myJobIsStressful          :: Maybe Int
  , moraleIsGoodWhereIWork    :: Maybe Int
  , iGetFrustratedAtWork      :: Maybe Int
  }

There's a very general function in the module Control.Monad which I'd
like to use just for the Maybe type here. It's called mplus, and for
Maybe, it works like this:

mplus (Just x) _ = Just x
mplus Nothing  y = y

So if the first parameter isn't Nothing, that's what you get,
otherwise, you get the second parameter. Of course, this operation has
an identity element which is Nothing.

So this lets you combine partial information expressed by Maybe types,
in a left-biased way.

It's about to become obvious that record types are less convenient
than perhaps they could be in Haskell, and this is absolutely true --
I'd actually probably use a somewhat different representation myself
(possibly something involving a Map from Strings (field names) to Int
values), but I can't really be sure what you intend with this data,
and how much type safety you want.

I'll elide the field names just because I can here. It's not
necessarily good style.

combine :: Entry -> Entry -> Entry
combine (Entry a1 a2 a3 a4 a5 a6 a7 a8) (Entry b1 b2 b3 b4 b5 b6 b7 b8)
    = Entry (a1 `mplus` b1) (a2 `mplus` b2) (a3 `mplus` b3) (a4 `mplus` b4)
            (a5 `mplus` b5) (a6 `mplus` b6) (a7 `mplus` b7) (a8 `mplus` b8)

Even with all the shorthand, this is pretty ugly (and I'll show how
I'd represent the data in a moment), but what this does is to combine
two partial entries, favouring the information in the
first, but filling the holes in the first with data from the second.
This operation has an identity element, which is:

emptyEntry = Entry Nothing Nothing Nothing Nothing Nothing Nothing
Nothing Nothing

Let's try a different representation, which is a little more flexible,
but expresses less in the type system.

data Entry = Entry { occupation :: Maybe String, survey :: M.Map String Int }
   deriving (Eq, Ord, Show)

So now, instead of a bunch of separate Maybe Int fields, we have just
one Map from String to Int. If we don't have information for a field,
we simply won't have that key in the Map. Of course, this means we'll
have to use strings for field labels. If that seems unhappy, you could
always define a type like:

data SurveyQuestion = IEnjoyMyJob
                    | MyJobIsWellDefined
                    | MyCoworkersAreCooperative
                    | MyWorkplaceIsStressful
                    | MyJobIsStressful
                    | MoraleIsGoodWhereIWork
                    | IGetFrustratedAtWork
    deriving (Eq, Ord, Show)

to be used in place of the String type.

Let's see how combine will look now:

combine :: Entry -> Entry -> Entry
combine (Entry o1 s1) (Entry o2 s2) = Entry (o1 `mplus` o2) (s1 `M.union` s2)

Or, using the record syntax more:

combine :: Entry -> Entry -> Entry
combine e1 e2 = Entry { occupation = (occupation e1 `mplus` occupation e2),
                        survey = (survey e1 `M.union` survey e2) }

Again, this new version has an identity with respect to combine, which is:

emptyEntry = Entry {occupation = Nothing, survey = (M.empty)}

Now, we just need a way to take one of your rows, and turn it into a
(key,value) pair, where the value is a partial entry.

readRow :: [String] -> (Int, Entry)
readRow [n, k, "occupation", v] = (read k, emptyEntry { occupation = Just v })
readRow [n, k, f, v] = (read k, emptyEntry { survey = M.singleton f (read v) })
readRow xs = error "failure case, should never happen!"

There is actually a failure case that I'm not handling here, which is
what happens when the value or key fails to parse as an Int. For that
we'd use reads instead of read, but let's ignore it for now.

We can then map this function over our cut up rows, something along
the lines of:

map readRow (cutUp text)

at which point we'll have a list of (Int, Entry) pairs.

We then want to fill up our Entries Map with those, and we want to
combine them as we go using the combine function:

entryMap text = M.fromListWith combine (map readRow (cutUp text))

Some final changes we could consider would be putting more of the
error handling into readRow itself: if it was to return a singleton
Map rather than an (Int, Entry) pair, then it could return the empty
Map on failure, and the results would then be combined using the
function M.unionsWith combine. We could move the length 4 test out of
cutUp then, and just make it the fall-through case in readRow. I'll
also use reads, which returns a list of (parse,rest-of-string) pairs,
to handle the failure cases where the numbers don't parse, by just
treating those rows as nonexistent:

readRow :: [String] -> M.Map Int Entry
readRow [n, k, f, v] =
   case reads k of
      []       -> M.empty -- the key didn't parse
      (k',_):_ ->
          if f == "occupation"
             then M.singleton k' (emptyEntry { occupation = Just v })
             else case reads v of
                    []       -> M.empty -- the value didn't parse
                    (v',_):_ -> M.singleton k'
                                    (emptyEntry { survey = M.singleton f v' })
readRow xs = M.empty -- this handles the case when the list is any length but 4

cutUp text = tail (map (\x -> split x '\t') (lines text)) -- which
allows cutUp to be simpler

entryMap text = M.unionsWith combine (map readRow (cutUp text))

Anyway, I hope this tutorial gives some idea of how things progress,
and what sort of thinking is generally involved. Note that the focus
here was more on finding the right combining operations, and then
using pre-existing higher-order functions to collapse the structure,
than it was on recursion. Indeed, the final program there doesn't
contain any explicit recursion at all! This is generally something to
aim for. Using explicit recursion is generally a means of last resort.
Higher order functions on data structures are our control structures.

 - Cale
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@...
http://www.haskell.org/mailman/listinfo/haskell-cafe

Re: Doubting Haskell

by Alan Carter-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Cale,

On Feb 20, 2008 10:58 AM, Cale Gibbard <cgibbard@...> wrote:
> (I'm copying the list on this, since my reply contains a tutorial
> which might be of use to other beginners.)

Thank you so much for this - I've just started playing with it so few
intelligent responses yet. I'm sure it will be of *huge* use to
others, right in the middle of the "gap" I fell into.

The experiment continues - I'll be back :-)

Many thanks,

Alan

--
... the PA system was moaning unctuously, like a lady hippopotamus
reading A. E. Housman ..."
  -- James Blish, "They Shall Have Stars"
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@...
http://www.haskell.org/mailman/listinfo/haskell-cafe

Re: Doubting Haskell

by Yitzchak Gale :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Cale Gibbard wrote:
>  I woke up rather early, and haven't much to do, so I'll turn this into
>  a tutorial. :)

Cale, this is fantastic, as always. I often find myself
searching for material like this when introducing
people to Haskell.

Would you be willing to put this on the wiki?

Thanks,
Yitz
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@...
http://www.haskell.org/mailman/listinfo/haskell-cafe

Re: Doubting Haskell

by Thomas Davie :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

A quick note here.  This is a *really* excellent tutorial on a variety  
of subjects.  It shows how monad operators can be used responsibly (to  
clarify code, not obfuscate it), it shows how chosing a good data  
structure and a good algorithm can work wonders for your code, and on  
a simplistic level, it shows how to build a database in Haskell.

Would it be possible to clean this up and put it in the wiki somewhere?

Thanks

Bob

On 20 Feb 2008, at 09:58, Cale Gibbard wrote:

> (I'm copying the list on this, since my reply contains a tutorial
> which might be of use to other beginners.)
>
> On 19/02/2008, Alan Carter <alangcarter@...> wrote:
>> Hi Cale,
>>
>> On Feb 19, 2008 3:48 PM, Cale Gibbard <cgibbard@...> wrote:
>>> Just checking up, since you haven't replied on the list. Was my
>>> information useful? Did I miss any questions you might have had? If
>>> you'd like, I posted some examples of using catch here:
>>
>> Thanks for your enquiry! My experiment continues. I did put a  
>> progress
>> report on the list - your examples together with a similar long an
>> short pair got me over the file opening problem, and taught me some
>> things about active whitespace :-) I couldn't get withFile working
>> (says out of scope, maybe 'cos I'm ghc 6.6 on my Mac)
>
> Make sure to put:
>
> import System.IO
>
> at the top of your source file, if you haven't been. This should
> import everything documented here:
> http://www.haskell.org/ghc/docs/latest/html/libraries/base/System-IO.html
>
>> but it turned out the line I was looking for (collapsed from the  
>> examples)
>> was:
>>
>>  text <- readFile "data.txt" `catch` \_ -> return ""
>>
>> This ensures the program never loses control, crashing or becoming
>> unpredictable by attempting to use an invalid resource, by yielding  
>> an
>> empty String if for any reason the file read fails. Then an empty
>> String makes it very quickly through parsing. I guess that's quite
>> "functiony" :-)
>>
>> Amazing how easy once I knew how. Even stranger that I couldn't  
>> find a
>> "bread and butter" example of it.
>>
>> Then I was going very quickly for a while. My file is dumped from a
>> WordPress MySql table. Well formed lines have 4 tab separated fields
>> (I'm using pipes for tabs here):
>>
>> line id | record id | property | value
>>
>> Line IDs are unique and don't matter. All lines with the same record
>> ID give a value to a property in the same record, similar to this:
>>
>> 1|1|name|arthur
>> 2|1|quest|seek holy grail
>> 3|1|colour|blue
>> 4|2|name|robin
>> 5|2|quest|run away
>> 6|2|colour|yellow
>>
>> Organizing that was a joy. It took minutes:
>
> let cutUp = tail (filter (\fields -> (length fields) == 4)
>                                      (map (\x -> split x '\t')  
> (lines text)))
>
> This should almost certainly be a function of text:
>
> cutUp text = tail (filter (\fields -> (length fields) == 4)
>                                 (map (\x -> split x '\t') (lines  
> text)))
>
>> I found a split on someone's blog (looking for a library tokenizer),
>> but I can understand it just fine. I even get to chuck out ill-formed
>> lines and remove the very first (which contains MySql column names)  
>> on
>> the way through!
>
> Sadly, there's no general library function for doing this. We have
> words and lines (and words would work here, if your fields never have
> spaces), but nobody's bothered to put anything more general for simple
> splitting into the base libraries (though I'm sure there's plenty on
> hackage -- MissingH has a Data.String.Utils module which contains
> split and a bunch of others, for example). However, for anything more
> complicated, there are also libraries like Parsec, which are generally
> really effective, so I highly recommend looking at that at some point.
>
>> I then made a record to put things in, and wrote some lines to play
>> with it (these are the real property names):
>>
>> data Entry = Entry
>>  { occupation                :: String
>>  , iEnjoyMyJob               :: Int
>>  , myJobIsWellDefined        :: Int
>>  , myCoworkersAreCooperative :: Int
>>  , myWorkplaceIsStressful    :: Int
>>  , myJobIsStressful          :: Int
>>  , moraleIsGoodWhereIWork    :: Int
>>  , iGetFrustratedAtWork      :: Int
>>  }
>> ...
>>  let e = Entry{occupation = "", iEnjoyMyJob = 0}
>>  let f = e {occupation = "alan"}
>>  let g = f {iEnjoyMyJob = 47}
>>  putStrLn ((occupation g) ++ " " ++ (show (iEnjoyMyJob g)))
>>
>> Then I ran into another quagmire. I think I have to use Data.Map to
>> build a collection of records keyed by record id, and fill them in by
>> working through the list of 4 item lists called cutUp. As with the
>> file opening problem I can find a few examples that convert a list of
>> tuples to a Data.Map, one to one. I found a very complex example that
>> convinced me a map from Int to a record is possible, but gave me no
>> understanding of how to do it. I spent a while trying to use foldl
>> before I decided it can't be appropriate (I need to pass more  
>> values).
>> So I tried a couple of recursive functions, something like:
>>
>> type Entries = M.Map Int Entry
>> ...
>>  let entries = loadEntries cutUp
>> ...
>> loadEntries :: [[String]] -> Entries
>> loadEntries [] = M.empty Entries
>> loadEntries [x : xs] = loadEntry (loadEntries xs) x
> -- Possible common beginner error here: [x:xs] means the list with one
> element which is a list whose first element is x and whose tail is xs.
> Your type signature and the type of cutUp seems to confirm that this
> is the right type, but you don't seem to have a case to handle a
> longer list of lists. If you want just a list with first entry x, and
> with tail xs, that's just (x:xs). If you want to handle lists of lists
> recursively, you'll generally need two cases: ([]:xss) and
> ((x:xs):xss). We'll end up doing something different instead of
> recursion in a moment.
>>
>> loadEntry entries _ rid fld val = entries
>>
>> Trying to create an empty map at the bottom of the recursion so later
>> I can try to fiddle about checking if the key is present and  
>> crating a
>> new record otherwise, then updating the record with a changed one (a
>> big case would be needed deep in to do each property update). If I'm
>> on the right track it's not good enough to get better, so now I'm  
>> just
>> throwing bits of forest animals into the pot at random again :-(
>>
>> So I certainly would be grateful for a clue! The bits I can do (I got
>> a non-trivial wxHaskell frame sorted out quite easily, the tokenizing
>> and record bit were OK) I think show I'm not *totally* stupid at  
>> this,
>> I'm putting loads of time investment in (it's an experiement in
>> itself) but there do seem to be certain specific things that would be
>> ubiquitous patterns in any production or scripting environment, which
>> are not discussed at all and far from obvious. The more I see of
>> Haskell the more I suspect this issue is the gating one for popular
>> uptake.
>>
>> I couldn't help thinking of this bit, from the Wikipedia entry on the
>> Cocteau Twins:
>>
>> "The band's seventh LP, Four-Calendar Café, was released in late  
>> 1993.
>> It was a departure from the heavily-processed, complex and layered
>> sounds of Blue Bell Knoll and Heaven or Las Vegas, featuring clearer
>> and more minimalistic arrangements. This, along with the record's
>> unusually comprehensible lyrics, led to mixed reviews for the album:
>> Some critics accused the group of selling out and producing an
>> 'accessible album,' while others praised the new direction as a
>> felicitous development worthy of comparison with Heaven or Las  
>> Vegas."
>>
>> Best wishes,
>>
>> Alan
>
> I woke up rather early, and haven't much to do, so I'll turn this into
> a tutorial. :)
>
> Okay. The most common ways to build a map are by using the fromList,
> fromListWith, or fromListWithKey functions. You can see them in the
> documentation here:
>
> http://www.haskell.org/ghc/docs/latest/html/libraries/containers/Data-Map.html#v%3AfromList
>
> The types are:
>
> fromList :: (Ord k) => [(k,a)] -> Map k a
>
> fromListWith :: (Ord k) => (a -> a -> a) -> [(k,a)] -> Map k a
>
> fromListWithKey :: (Ord k) => (k -> a -> a -> a) -> [(k,a)] -> Map k a
>
> They take a list of (key,value) pairs, and build a map from it.
> Additionally, the fromListWith function takes a function which
> specifies how the values should be combined if their keys collide.
> There is also a fromListWithKey function which allows the means of
> combination to depend on the key as well.
>
> At this point we realise something interesting about the way the data
> is being represented: if there is a field in someone's record with no
> row in the database, what should the resulting field contain? In C,
> they often use some integer which is out of range, like -1 for this.
>
> How about for a missing occupation field? Well, that's a String, you
> could use some generic failure string, or an empty string, but I'll
> show you another possibility that just might be convenient.
>
> If t is any type, then the type (Maybe t) consists of the values
> Nothing, and Just x, whenever x is a value of type t. This is another
> convenient way to represent the idea that a computation might fail.
>
> Let's start by changing your record type so that each field is a Maybe
> value, that is, either the value Nothing, or the value Just x, where x
> is the value it would have been.
>
> data Entry = Entry
>  { occupation                :: Maybe String
>  , iEnjoyMyJob               :: Maybe Int
>  , myJobIsWellDefined        :: Maybe Int
>  , myCoworkersAreCooperative :: Maybe Int
>  , myWorkplaceIsStressful    :: Maybe Int
>  , myJobIsStressful          :: Maybe Int
>  , moraleIsGoodWhereIWork    :: Maybe Int
>  , iGetFrustratedAtWork      :: Maybe Int
>  }
>
> There's a very general function in the module Control.Monad which I'd
> like to use just for the Maybe type here. It's called mplus, and for
> Maybe, it works like this:
>
> mplus (Just x) _ = Just x
> mplus Nothing  y = y
>
> So if the first parameter isn't Nothing, that's what you get,
> otherwise, you get the second parameter. Of course, this operation has
> an identity element which is Nothing.
>
> So this lets you combine partial information expressed by Maybe types,
> in a left-biased way.
>
> It's about to become obvious that record types are less convenient
> than perhaps they could be in Haskell, and this is absolutely true --
> I'd actually probably use a somewhat different representation myself
> (possibly something involving a Map from Strings (field names) to Int
> values), but I can't really be sure what you intend with this data,
> and how much type safety you want.
>
> I'll elide the field names just because I can here. It's not
> necessarily good style.
>
> combine :: Entry -> Entry -> Entry
> combine (Entry a1 a2 a3 a4 a5 a6 a7 a8) (Entry b1 b2 b3 b4 b5 b6 b7  
> b8)
>    = Entry (a1 `mplus` b1) (a2 `mplus` b2) (a3 `mplus` b3) (a4  
> `mplus` b4)
>            (a5 `mplus` b5) (a6 `mplus` b6) (a7 `mplus` b7) (a8  
> `mplus` b8)
>
> Even with all the shorthand, this is pretty ugly (and I'll show how
> I'd represent the data in a moment), but what this does is to combine
> two partial entries, favouring the information in the
> first, but filling the holes in the first with data from the second.
> This operation has an identity element, which is:
>
> emptyEntry = Entry Nothing Nothing Nothing Nothing Nothing Nothing
> Nothing Nothing
>
> Let's try a different representation, which is a little more flexible,
> but expresses less in the type system.
>
> data Entry = Entry { occupation :: Maybe String, survey :: M.Map  
> String Int }
>   deriving (Eq, Ord, Show)
>
> So now, instead of a bunch of separate Maybe Int fields, we have just
> one Map from String to Int. If we don't have information for a field,
> we simply won't have that key in the Map. Of course, this means we'll
> have to use strings for field labels. If that seems unhappy, you could
> always define a type like:
>
> data SurveyQuestion = IEnjoyMyJob
>                    | MyJobIsWellDefined
>                    | MyCoworkersAreCooperative
>                    | MyWorkplaceIsStressful
>                    | MyJobIsStressful
>                    | MoraleIsGoodWhereIWork
>                    | IGetFrustratedAtWork
>    deriving (Eq, Ord, Show)
>
> to be used in place of the String type.
>
> Let's see how combine will look now:
>
> combine :: Entry -> Entry -> Entry
> combine (Entry o1 s1) (Entry o2 s2) = Entry (o1 `mplus` o2) (s1  
> `M.union` s2)
>
> Or, using the record syntax more:
>
> combine :: Entry -> Entry -> Entry
> combine e1 e2 = Entry { occupation = (occupation e1 `mplus`  
> occupation e2),
>                        survey = (survey e1 `M.union` survey e2) }
>
> Again, this new version has an identity with respect to combine,  
> which is:
>
> emptyEntry = Entry {occupation = Nothing, survey = (M.empty)}
>
> Now, we just need a way to take one of your rows, and turn it into a
> (key,value) pair, where the value is a partial entry.
>
> readRow :: [String] -> (Int, Entry)
> readRow [n, k, "occupation", v] = (read k, emptyEntry { occupation =  
> Just v })
> readRow [n, k, f, v] = (read k, emptyEntry { survey = M.singleton f  
> (read v) })
> readRow xs = error "failure case, should never happen!"
>
> There is actually a failure case that I'm not handling here, which is
> what happens when the value or key fails to parse as an Int. For that
> we'd use reads instead of read, but let's ignore it for now.
>
> We can then map this function over our cut up rows, something along
> the lines of:
>
> map readRow (cutUp text)
>
> at which point we'll have a list of (Int, Entry) pairs.
>
> We then want to fill up our Entries Map with those, and we want to
> combine them as we go using the combine function:
>
> entryMap text = M.fromListWith combine (map readRow (cutUp text))
>
> Some final changes we could consider would be putting more of the
> error handling into readRow itself: if it was to return a singleton
> Map rather than an (Int, Entry) pair, then it could return the empty
> Map on failure, and the results would then be combined using the
> function M.unionsWith combine. We could move the length 4 test out of
> cutUp then, and just make it the fall-through case in readRow. I'll
> also use reads, which returns a list of (parse,rest-of-string) pairs,
> to handle the failure cases where the numbers don't parse, by just
> treating those rows as nonexistent:
>
> readRow :: [String] -> M.Map Int Entry
> readRow [n, k, f, v] =
>   case reads k of
>      []       -> M.empty -- the key didn't parse
>      (k',_):_ ->
>          if f == "occupation"
>             then M.singleton k' (emptyEntry { occupation = Just v })
>             else case reads v of
>                    []       -> M.empty -- the value didn't parse
>                    (v',_):_ -> M.singleton k'
>                                    (emptyEntry { survey =  
> M.singleton f v' })
> readRow xs = M.empty -- this handles the case when the list is any  
> length but 4
>
> cutUp text = tail (map (\x -> split x '\t') (lines text)) -- which
> allows cutUp to be simpler
>
> entryMap text = M.unionsWith combine (map readRow (cutUp text))
>
> Anyway, I hope this tutorial gives some idea of how things progress,
> and what sort of thinking is generally involved. Note that the focus
> here was more on finding the right combining operations, and then
> using pre-existing higher-order functions to collapse the structure,
> than it was on recursion.

_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@...
http://www.haskell.org/mailman/listinfo/haskell-cafe

Re: Doubting Haskell

by Alan Carter-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Many thanks for the explanations when I was first experimenting with
Haskell. I managed to finish translating a C++ wxWidgets program into
Haskell wxHaskell, and am certainly impressed.

I've written up some reflections on my newbie experience together with
both versions, which might be helpful to people interested in
popularizing Haskell, at:

http://the-programmers-stone.com/2008/03/04/a-first-haskell-experience/

Regards,

Alan

--
... the PA system was moaning unctuously, like a lady hippopotamus
reading A. E. Housman ..."
  -- James Blish, "They Shall Have Stars"
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@...
http://www.haskell.org/mailman/listinfo/haskell-cafe

Re: Doubting Haskell

by Paul Johnson-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Alan Carter wrote:
> I've written up some reflections on my newbie experience together with
> both versions, which might be helpful to people interested in
> popularizing Haskell, at:
>
> http://the-programmers-stone.com/2008/03/04/a-first-haskell-experience/
>  
Thank you for writing this.

On the lack of simple examples showing, for example, file IO: I seem to
recall a Perl book (maybe it was Edition 1 of the Camel Book) which had
lots of very short programs each illustrating one typical job.  Also the
Wiki does have some pages of "worked example" programs.  But I agree, we
could do better.

I'm surprised you found the significant whitespace difficult.  Yes, the
formal rules are a bit arcane, but I just read them as "does the Right
Thing", and it generally works for me.  I didn't know about the
significance of comments, but then I've never written an outdented comment.

I had a look through your code, and although I admit I haven't done the
work, I'm sure that there would be ways of factoring out all the
commonality and thereby reducing the length.

Finally, thanks for that little story about the BBC B.  I had one of
those, and I always wondered about that heatsink, and the stonking big
resistor next to it.  They looked out of scale with the rest of the board.

Paul.

_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@...
http://www.haskell.org/mailman/listinfo/haskell-cafe

Re: Doubting Haskell

by Ketil Malde-5 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Paul Johnson <paul@...> writes:

> I'm surprised you found the significant whitespace difficult.

I wonder if this has something to do with the editor one uses?  I use
Emacs, and just keep hitting TAB, cycling through possible alignments,
until things align sensibly.  I haven't really tried, but I can
imagine lining things up manually would be more painful, especially
if mixing tabs and spaces.

-k
--
If I haven't seen further, it is by standing in the footprints of giants
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@...
http://www.haskell.org/mailman/listinfo/haskell-cafe

Re: Doubting Haskell

by Luke Palmer-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Tue, Mar 4, 2008 at 4:16 AM, Ketil Malde <ketil@...> wrote:
> Paul Johnson <paul@...> writes:
>
>  > I'm surprised you found the significant whitespace difficult.
>
>  I wonder if this has something to do with the editor one uses?  I use
>  Emacs, and just keep hitting TAB, cycling through possible alignments,
>  until things align sensibly.  I haven't really tried, but I can
>  imagine lining things up manually would be more painful, especially
>  if mixing tabs and spaces.

Especially if mixing tabs and spaces indeed.  Haskell does the Python
thing of assuming that a tab is 8 spaces, which IMO is a mistake.  The
sensible thing to do if you have a whitespace-sensitive language that
accepts both spaces in tabs is to make them incomparable to each
other; i.e.


    main = do
    <sp><sp>putStrLn $ "Hello"
    <sp><sp><tab>++ "World"
    -- compiles fine


    main = do
    <sp><sp>putStrLn $ "Hello"
    <tab>++ "World"
    -- error, can't tell how indented '++ "World"' is...

Luke
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@...
http://www.haskell.org/mailman/listinfo/haskell-cafe

Re: Doubting Haskell

by Paul Moore-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On 04/03/2008, Alan Carter <alangcarter@...> wrote:
> http://the-programmers-stone.com/2008/03/04/a-first-haskell-experience/

That was an interesting read. Thanks for posting it. I also liked the
tale of the BBC ULA - it reminded me of a demo I saw once at an Acorn
show, where they had a RISC PC on show, with a (IBM) PC card in it.
They were demonstrating how hot the PC chip runs compared to the ARM
RISC chip by using it to make toast. I dread to think what you could
do with one of today's monsters :-)

Paul.
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@...
http://www.haskell.org/mailman/listinfo/haskell-cafe
< Prev | 1 - 2 - 3 | Next >