// Try Haskell 1.0.1 // Tue Feb 23 18:34:48 GMT 2010 // // Copyright 2010 Chris Done. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // // 1. Redistributions of source code must retain the above // copyright notice, this list of conditions and the following // disclaimer. // 2. Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials // provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY CHRIS DONE ``AS IS'' AND ANY EXPRESS // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL CHRIS DONE OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT // OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH // DAMAGE. // The views and conclusions contained in the software and // documentation are those of the authors and should not be // interpreted as representing official policies, either expressed or // implied, of Chris Done. // // TESTED ON // Internet Explorer 6 // Opera 10.01 // Chromium 4.0.237.0 (Ubuntu build 31094) // Firefox 3.5.8 // Temporary fix function opera(){ return navigator.userAgent.indexOf("Opera") == 0; } function encodeHex(str){ var result = ""; for (var i=0; i0;){ i-=4; var digit = (n>>i) & 0xf; if (!start || digit != 0){ start = false; result += digitArray[digit]; } } return (result==''?'0':result); } (function($){ var raphaelPaper; var raphaelObjs; var tutorialGuide; // Page variables // var nemesis = 'chirs'; var showTypes = false; var pages = [ //////////////////////////////////////////////////////////////////////// // Lesson 1 // Simple addition {lesson:1, title:'Basics; numbers, strings, etc.', guide: '

' + rmsg(['Learning By Numbers','Music is Math','Back to Basics']) + '

' + "

To kick off let's try some maths out. Up there you can" + " type in Haskell expressions. Try this out: 5 + 7

" }, {guide:function(result){ if (!result) result = {expr:'5+7',result:12}; var complied = result.expr.replace(/ /g,'')=="5+7"; var who = complied? 'we' : 'you'; return '

' + rmsg(['Your first Haskell expression', "First Time's a Charm"]) + '

' + '

Well done, you typed it perfect! You got back the number'+ ' ' + result.result + '. Just what '+who+' wanted. ' + "

Let's try something completely different."+ " Type in your name like this:" + ' "chris"

' }, trigger:function(result){ return result.type == "(Num t) => t" || result.type == "Integer" || result.type == "Int"; } }, // Strings & types {guide:function(result){ if (!result) result = {expr:'"chris"',result:"\"chris\""}; var n = unString(result.result); if (n) n = ", " +n; n += "!"; return '

' + rmsg(['Types of values',"What's in a name?"]) + '

' + '

Hi there' + htmlEncode(n) + (n!="!"? " That's a pretty name. Honest." : "") + " You're getting the hang of this!

" + "

Note: You can chat to Haskell programmers while learning here, enter chat to start it."+ " You will join the official IRC channel of the Haskell community!

" + "

Each time, you're getting back the value of the expression. So "+ "far, just a number and a list of characters.

" + "

You can have lists of other stuff, too. Let's see your " + " lottery numbers: [42,13,22]

" }, trigger:function(result){ return result.type == "[Char]" || result.type == "String"; } }, // Overview of lesson 1 {guide:function(result){ if (!result) result = {result:"[42,13,22]"}; return '

' + rmsg(["Lesson 1 done already!"]) + '

' + "

Great, you made a list of numbers! If you win we'll split" + " the winnings, right?

" + "

Let's see what you've learned so far:

" + "
    "+ "
  1. How to write maths and lists of things.
  2. "+ "
" + "

You can do stuff with lists. Maybe you want the lottery "+ "numbers sorted in the right order, try this: " + "sort " + result.result + "

" }, trigger:function(result){ return result.expr.match(/^[ ]*\[[0-9, ]+\][ ]*$/) && result.type == "(Num t) => [t]"; } }, //////////////////////////////////////////////////////////////////////// // Lesson 2 - Functions // Functions on lists {lesson:2, title: 'Simple Functions', guide:function(result){ if (!result) result = {result:"[13,23,30]"}; return '

' + rmsg(["We put the funk in function"]) + '

' + "

Congratulations, you just used a function."+ " They're how you get things done in Haskell." + "

As you might've guessed, we got back " + htmlEncode(result.result) + ".

Ever wanted an evil twin nemesis? Me too. "+ "Luckily, you can sort lists of characters, or "+ "strings" + ", in the same way as numbers! sort \"chris\"

" }, trigger:function(result){ return result.expr.match(/sort/) && result.type == "(Num t, Ord t) => [t]"; } }, // Tuples {guide:function(result){ if (!result) result = {result:"\"chirs\""}; nemesis = htmlEncode(unString(result.result)); return '

' + rmsg(["Tuples, because sometimes one value ain't enough!"]) + '

' + "

Watch out for "+nemesis+"! You should keep their credentials for the police.

" + "

My nemesis is 28 years of age: "+ "(28,\"chirs\")

" }, trigger:function(result){ return result.expr.match(/sort/) && result.type == "[Char]"; } }, // Functions on tuples {guide:function(result){ if (!result) result = {result:"(28,\"chirs\")"}; var age = result.result.match(/^\(([0-9]+)+/); var villain = htmlEncode(result.result.replace(/\\"/g,'"')); return '

' + rmsg(["We'll keep them safe, sir."]) + '

' + "

Is "+(age?age[1]:"that")+" a normal age for a " + "super-villain?

" + "

You just wrote a tuple. It's a way to keep a bunch of values together in Haskell. " + "You can put as many as you like in there:

" + "" + "

Actually, let's say our villain is " + "" + villain + "" + ", how do you get their age?

" + "fst " + villain + "" }, trigger:function(result){ return result.expr.match(/\([0-9]+,[ ]*"[^"]+"\)/) && result.type == "(Num t) => (t, [Char])"; } }, // Summary of lesson 2 {guide:function(result){ return '

' + rmsg(["Lesson 2 done! Wow, great job!", "Lesson 2 completo!"]) + '

' + "

Good job! You got the age back from the tuple! Didn't " + " even break a sweat, did you? The fst function "+ "just gets the first value. It's called \"fst\" because " + "it's used a lot in Haskell so it really needs to be short!

" + "

Time to take a rest and see what you learned:

" + "
    "+ "
  1. Functions can be used on lists of any type.
  2. " + "
  3. We can stuff values into tuples.
  4. " + "
  5. Getting the values back from tuples is easy.
  6. "+ "
" + "

Now let's say you want " + " to use a value more than once, how would you do it? "+ "To make our lives easier, we can say:

" + "let x = 4 in x * x" }, trigger:function(result){ return result.expr.match(/fst/) && result.type == "(Num t) => t"; } }, {guide:function(result){ return "

Let them eat cake

" + "

You just bound a variable. " + "That is, you bound x to the expression 4, " + " and then you can write x in some code (the body) and " + " it will mean the same as if you'd written 4.

" + "

It's like this: let var = expression in body

" + "The in part just separates the expression from the body.

" + "

For example try: " + "let x = 8 * 10 in x + x

" + "

So if we wanted to get the age of our villain, we could do:

" + "let villain = (28,\"chirs\") in fst villain" },trigger:function(result){ return result.expr.match(/^[ ]*let[ ]+x[ ]*=[ ]*[0-9]+[ ]*in[ ]*x[ ]*\*[ ]*x/) && result.type == "(Num t) => t"; } }, {guide:function(result){ return "

Basics over, let's go!

" + "

Next, let's take a short detour to learn about " + "syntactic sugar. " + "Try typing this out:

" + "

'a' : []

" + "

Or skip to lesson4 to learn about functions," + " the meat of Haskell!"; },trigger:function(result){ return result.expr.match(/^[ ]*let[ ]+villain[ ]*=[ ]*\([0-9]+,[ ]*"[^"]+"\)[ ]*in[ ]+fst[ ]+villain[ ]*/) && result.type == "(Num t) => t"; } }, // Lesson 3: Syntactic sugar {lesson:3, title:'Syntactic Sugar', guide:function(result){ return '

' + rmsg(["You constructed a list!"]) + '

' + "

Well done, that was tricky syntax. You used the (:) " + "function. It takes two values, some value and a list, and " + " constructs a new list" + " out of them. We call it 'cons' for short.

" + "

'a' is " + "the character 'a', [] is an empty list. So " + "tacking 'a' at the start of an empty list just "+ "makes a list ['a']!

" + "

But thankfully we don't have to type out " + "'a' : 'b' : [] every time to we want to make a "+ "list of characters; we can use " + "syntactic sugar and just write"+ " ['a','b']. Don't believe me, check this!

" + "'a' : 'b' : [] == ['a','b']" }, trigger:function(result){ return result.expr.match(/^[ ]*'a'[ ]*:[ ]*\[\][ ]*/) && result.type == "[Char]"; } }, // Booleans and string syntactic sugar {guide:function(result){ return '

' + rmsg(["You're on fire!"]) + '

' + "

You're handling this syntax really well, nice!

" + "

You just got a boolean value back, and it said " + "True. That means they're equal!

" + "

One final demonstration on syntactic sugar for now:

" + "['a','b','c'] == \"abc\"" }, trigger:function(result){ return result.type == "Bool" && result.expr.replace(/[^':\[\]\=,]/g,'') == "'':'':[]==['','']"; } }, // Summary of syntactic sugar section {guide:function(result){ return '

' + rmsg(["Lesson 3 over! Syntactic sugar is sweet"]) + '

' + "

Let's have a gander at what you learned:

" + "
    " + "
  1. In 'a' : [], : is really just " + " another function, just clever looking.
  2. " + "
  3. Pretty functions like this are written like (:) when " + " you talk about them.
  4. " + "
  5. A list of characters ['a','b'] can just be written " + "\"ab\". Much easier!
  6. " + "
" + "

Phew! You're getting pretty deep! Your arch nemesis, " + nemesis + ", is gonna try to steal your " + rmsg(['mojo', 'pizza']) + "! Let's learn a bit more about functions and passing " + "them around. Try this:

map (+1) [1..5]

"; }, trigger:function(result){ return result.expr.replace(/[^\]\[',=\"]?/g,'') == "['','','']==\"\"" && result.type == "Bool"; } }, {lesson:4, title:'Functions, reloaded; passing, defining, etc.', guide:function(){ var title = rmsg(["Functions [of a Geisha]", "Functions, functors, functoids, funky", "Functions: Expanded fo' real"]); return "

" + title + "

" + "

Here's where the magic begins!

" + "

You just passed the (+1) " + "function to the map function.

" + "

You can try other things like (remember: click to insert them):

" + "" + "

Note that a tuple is different to a list because you can do this:

" + "(1,\"George\")" }, trigger:function(result){ return result.expr.match(/^[ ]*map[ ]+\(\+1\)[ ]*\[1..5\][ ]*$/) && result.type == "(Num a, Enum a) => [a]"; }}, {guide:function(result){ return "

Lists and Tuples

" + "

You can only " + " have a list of numbers or a list of characters, whereas in a tuple you can throw anything in!

" + "

We've also seen that you can make a new list with (:) that joins two values together, like:

" + "

1 : [2,3]

" + "

But we can't do this with tuples! You can only write a tuple and then look at what's inside. You can't make new ones on the fly like a list." + "

Let's write our own functions! It's really easy. How about something simple:

" + "let square x = x * x in square "+rmsg([52,10,3])+"" }, trigger:function(result){ return result.expr.match(/^[ ]*\(1,"[^"]+"\)[ ]*$/) && result.type == "(Num t) => (t, [Char])"; }}, {guide:function(result){ return "

Let there be functions

" + "

Nice one! I think you're getting used to the let syntax.

" + "

You defined a function. You can read it as, as for a given " + "parameter called x, square of " + "x is x * x." + "

Some others you can try are:

" + "" + "

Let's go crazy and use our square function with map:

" + "let square x = x * x in map square [1..10]" }, trigger:function(result){ return result.expr.match(/^[ ]*let[ ]*square[ ]+x[ ]*=[ ]*x[ ]*\*[ ]*x[ ]*in[ ]*square[ ]+[0-9]+/) && result.type == "(Num t) => t"; }}, {guide:function(result){ if (!result || !result.value) result = { value: "[1,4,9,16,25,36,49,64,81,100]" }; return "

Let there be functions

" + "

That's so cool! You described a simple function square and then " + "you just passed it to another function (map) and got back " + htmlEncode(result.value) + ", exactly what you expected!

" + "

Haskell is pretty good at composing things together like this. " + "Some other things you can try are:

" + "" + "

Did you get back what you expected?

" + "

One more example for text; how do you upcase a letter?

" + "

toUpper 'a'

" }, trigger:function(result){ return result.expr.match(/^[ ]*let[ ]+square[ ]+x[ ]*=[ ]*x[ ]*\*[ ]*x[ ]*in[ ]+map[ ]+square[ ]*\[1..10\][ ]*$/) && result.type == "(Num a, Enum a) => [a]"; }}, {guide:function(result){ return "

Exercise time!

" + "

Easy! Remember: characters are written like 'a' and " + "strings (lists of characters) are written like \"a\"." + "

I need you to use toUpper capitalise my whole name, " + "\"Chris\". Give it a try." + " You can do it, I believe in you!

" + '

Spoiler: map toUpper "Chris"

' }, trigger:function(result){ return result.expr.match(/^toUpper 'a'$/) && result.type == "Char"; }}, {guide:function(result){ return "

Lesson 4 complete!

" + "

Brilliant! You're making excellent progress! " + "You just passed toUpper to map. No problem.

" + "

Let's go over what you've learned in this lesson:

" + "
    " + "
  1. Functions like map take other functions as parameters.
  2. " + "
  3. Functions like (+1), (>5) and "+ "square can be passed to other functions.
  4. " + "
  5. Defining functions is just a case of writing what "+ "to do with the parameters.
  6. " + "
" + "

Let's check out pattern matching; a way to "+ "get values from other values using patterns. Try this:

" + "

let (a,b) = (10,12) in a * 2

" + "

Or you can skip this section and go to straight to lesson6; types!

" }, trigger:function(result){ return result.type == "[Char]" && result.expr.match(/^map[ ]+toUpper/); }}, {lesson:5, title:'Pattern Matching', guide:function(result){ var title = rmsg(["And therefore, patterns emerge in nature.", "And Then Patterns", "Pattern matching!"]) return "

" + title + "

" + "

Good typing, sir!

" + "

So you had a value (10,12) and matched " + "it against a pattern (a,b), then you were able" + " to do stuff with the a and b!" + "

Note: Pattern matching (a,b) against "+ "(1,2) to get the a is the same as" + " doing fst (1,2), like you did in step7!

" + "

A pattern always matches the way the "+ "value was originally constructed. Remember that \"abc\" is " + "syntactic sugar for 'a' : 'b' : 'c' : [].

" + "

So you can get the characters from a string with patterns:

" + "let (a:b:c:[]) = \"xyz\" in a" }, trigger:function(result){ return result.expr.match(/^[ ]*let[ ]+\(a,b\)[ ]+=[ ]+\(10,12\)[ ]+in[ ]+a[ ]*\*[ ]*2[ ]*$/) && result.type == "(Num t) => t"; }}, {guide:function(result){ return "

"+rmsg(["Ignorance is bliss","Ignoring values"])+"

" + "

You're getting into tricky syntax, huh? I know you can handle it!

" + "

If you just want some of the values, you can ignore the others with _ (underscore) like this:

" + "

let (a:_:_:_) = \"xyz\" in a

" + "

In fact, (a:b:c:d) is short-hand for " + "(a:(b:(c:d))), so you can just ignore the rest in one go:

" + "let (a:_) = \"xyz\" in a" }, trigger:function(result){ return result.expr.match(/^[ ]*let[ ]+\(a:b:c:\[\]\)[ ]*=[ ]*\"xyz\"[ ]*in[ ]+a[ ]*$/) && result.type == "Char"; }}, {guide:function(result){ return "

"+rmsg(["Exercise!","Show me the money!"])+"

" + "

Try to get the 'a' value from this value using pattern matching:

" + "

(10,\"abc\")

" + "

Spoiler: let (_,(a:_)) = (10,\"abc\") in a

" }, trigger:function(result){ return result.expr.match(/^[ ]*let[ ]*\(a:_\)[ ]*=[ ]*"xyz"[ ]*in[ ]*a[ ]*$/) && result.type == "Char"; }}, {guide:function(result){ return "

"+rmsg(["Well done!","Brilliant!","Perfetto!"])+"

" + "

Wizard! I think you've got pattern-matching down.

" + "

If you're still a bit unsure, here are some other things you can try:

" + "" + "

You can also grab a whole value and pattern match on it (have your cake and eat it too):

" + "let abc@(a,b,c) = (10,20,30) in (abc,a,b,c)" }, trigger:function(result){ return result.expr.match(/^[ ]*let[ ]*\(_,\(?a:_\)?\)[ ]*=[ ]*\(10,\"abc\"\)[ ]*in[ ]*a[ ]*$/) && result.type == "Char"; }}, {guide:function(result){ return "

"+rmsg(["And that's the end of that chapter"])+"

" + "

That was easy, right?

" + "

Let's go over what you've learned in this lesson:

" + "
    " + "
  1. Values are pattern matched, or deconstructed, by writing however they were constructed.
  2. " + "
  3. Patterns let you use the values that you match.
  4. " + "
  5. You can ignore whichever values you want.
  6. " + "
  7. You can pattern match and keep hold of the original value too.
  8. " + "
" + "

Now we get to the Deep Ones. Types!

" + "

Consider the following value: 'a'

" }, trigger:function(result){ return result.type == "(Num t, Num t1, Num t2) => ((t, t1, t2), t, t1, t2)"; }}, {lesson:6, title:'Types', guide:function(result){ showTypes = true; return "

"+rmsg(["Types","What's in a Type?","Types & Values"])+"

" + "

What's this? Something new!

" + "

In Haskell there are types of values. Every value belongs to a type. To demonstrate this fact, I've sneakily enabled types to be " + "shown of every value in the console from now on.

" + "

The type of the value 'a' is Char (short for 'character', but you guessed that, right?).

" + "

You've seen the type of a character, now what about" + " a list of characters?

" + "\"Spartacus\"" }, trigger:function(result){ return result.type == 'Char'; }}, {guide:function(result){ showTypes = true; return "

"+rmsg(["Lists of stuff, types"])+"

" + "

I'm Spartacus!

" + "

Okay, so a list of characters has type [Char].

" + "

Notice that when we write a :: X it means the value a has type X. It's just a short-hand called a signature.

" + "

If you just want the type of a value, without actually evaluating it, you can just type:

" + ":t toUpper" }, trigger:function(result){ return result.expr.match(/"[^"]+"/) && result.type == '[Char]'; }}, {guide:function(result){ showTypes = true; return "

"+rmsg(["Function types"])+"

" + "

Woah! Hold your blinkin' 'orses! The type of toUpper reads: Char -> Char

" + "

It's pretty easy; a -> b means function from a to b. " + "So

toUpper :: Char -> Char means: for a" + " given character (Char value) a, toUpper a has type Char.

" + "

Some other things you can try are:

" + "" + "

The words function is pretty handy. Want to get a list of words from a sentence?

" + "words \"There's jam in my pants.\"" }, trigger:function(result){ return result.type == 'Char -> Char'; }}, {guide:function(result){ showTypes = true; return "

"+rmsg(["Mid-way review"])+"

" + "

The type of words was String -> [String]. You got a list of strings back! Just what you expected, right?

" + "

Let's take a rest in the middle of this lesson and go over what we've learned:

" + "
    "+ "
  1. All values in Haskell have a type. We describe the types of values with signatures, like True :: Bool.
  2. "+ "
  3. Functions are values too, and they have types, notated a -> b.
  4. "+ "
  5. Functions can be defined for any type to any other type.
  6. "+ "
  7. Humble reader has a thing for jammy pants.
  8. "+ "
" + "

But what if you have a type that can contain values of any type, like a tuple?

" + ":t fst" }, trigger:function(result){ return result.expr.match(/^[ ]*words[ ]*\"[^"]+\"[ ]*$/) && result.type == '[String]'; }}, {guide:function(result){ showTypes = true; return "

"+rmsg(["Polymorphic functions"])+"

" + "

Remember this one? I know you do! fst (1,2) is 1, right?

" + "

We read its type

" + "

fst :: (a, b) -> a

" + "

as: for all types a and b, the fst has type (a,b) to a. So the fst "+ "function works on a pair of values of any types! We call such a function polymorphic."+ "

" + "

Remember the drop function? Maybe you don't. I don't! Let's check out its type:

" + "

:t drop

" }, trigger:function(result){ return result.type == '(a, b) -> a'; }}, {guide:function(result){ showTypes = true; return "

"+rmsg(["Multi parameter functions"])+"

" + "

So the drop function has type

Int -> [a] -> [a].

" + "

This is something new. You've got two arrows! Relax. You can read

" + "

a -> b -> c as a -> (b -> c)

" + "

In other words, drop is a function from integers (Int values) to functions of lists to lists ([a] -> [a] values). Drop is a function to another function.

" + "

Check for yourself! :t drop 3

" }, trigger:function(result){ return result.type == 'Int -> [a] -> [a]'; }}, {guide:function(result){ showTypes = true; return "

"+rmsg(["Partial application"])+"

" + "

You've got a function of type [a] -> [a]! The drop function is considered a multi-parameter function. Remember the map function? Its parameters were a function and a list. Just another multi-parameter function.

" + "

You can add another parameter and, hey presto, you get a list!

" + "drop 3 \"hello!\"" }, trigger:function(result){ return result.type == '[a] -> [a]'; }}, {guide:function(result){ showTypes = true; return "

"+rmsg(["Higher order functions"])+"

" + "

'Lo bob! You've already used the map function loads. I wonder if you can guess its type?

" + "

map :: (a -> b) -> [a] -> [b] (spoiler)

" + "

It's okay to peek! Have a go at guessing these: filter, take

" + "

Tip: You can use parantheses to use more than one function. You want to double all the numbers over five? Psch!

" + "map (*2) (filter (>5) [10,2,16,9,4])" }, trigger:function(result){ return result.expr.match(/^[ ]*drop[ ]*[0-9]+[ ]*"[^"]+"[ ]*$/) && result.type == '[Char]'; }}, {guide:function(result){ showTypes = true; return "

"+rmsg(["Phew! Rest time!"])+"

" + "

Wow! You're doing so great! Have a look at what you know now!

" + "
    " + "
  1. Function parameters can be polymorphic; any type!
  2. " + "
  3. Functions can have multiple parameters by returning more functions.
  4. " + "
  5. You can wrap expressions in parentheses and apply functions to them as a whole value.
  6. " + "
" + "

You're really making great progress. Don't hesitate to sit and play in the console between chapters to get a good feel of it!

" + "

Stay tuned for more chapters on type classes and the meaning of :t 1, :t (*), etc.

" + learnMore }, trigger:function(result){ return result.type == '(Num a, Ord a) => [a]'; }} ]; var webchat; function runWebchat() { if (!webchat) { // Create webchat frame var webchat = $(''); webchat.attr('width',635); webchat.attr('height',500); webchat.css('float','left'); webchat.css('webkit-border-radius','3px'); webchat.css('moz-border-radius','3px'); webchat.css('border-radius','3px'); webchat.css('border','5px solid #eeeeee'); // Extend page wrap to fit console and chat $('.page-wrap').css({width:'1250px'}); $('.primary-content').css('margin-left',0); $('.page-wrap').append(webchat.css('margin-left','5px')); } } var pageTrigger = -1; var notices = []; var controller; // Console controller var learnMore; //////////////////////////////////////////////////////////////////////// // Unshow a string function unString(str){ return str.replace(/^"(.*)"$/,"$1").replace(/\\"/,'"'); } //////////////////////////////////////////////////////////////////////// // Random message from a list of messages function rmsg(choices) { return choices[Math.floor((Math.random()*100) % choices.length)]; } // Simple HTML encoding // Simply replace '<', '>' and '&' // TODO: Use jQuery's .html() trick, or grab a proper, fast // HTML encoder. function htmlEncode(text,shy){ return ( (''+text).replace(/&/g,'&') .replace(/'); handleJSON = function(r){ script.remove(); func(r); }; script.attr('src',url); $('body').append(script); } //////////////////////////////////////////////////////////////////////// // Create console var console = $('.console'); controller = console.console({ promptLabel: '> ', commandValidate:function(line){ if (line == "") return false; // Empty line is invalid else return true; }, cancelHandle:function(){ controller.commandRef.ignore = true; controller.finishCommand(); controller.report(); }, commandHandle:function(line,report){ controller.ajaxloader = $('

Loading...

'); var commandRef = {}; controller.currentLine = line; controller.commandRef = commandRef; controller.report = report; if (tellAboutRet) tellAboutRet.fadeOut(function(){ $(this).remove(); }); if (libTrigger(line,report)) return; controller.inner.append(controller.ajaxloader); controller.scrollToBottom(); jsonp("http://tryhaskell.org/haskell.json?method=eval&pad=handleJSON&expr=" + encodeHex(line) + "&random=" + Math.random(), function(resp){ if (commandRef.ignore) { return; } controller.finishCommand(); var result = resp; if (pageTrigger > -1 && result.expr) { triggerTutorialPage(pageTrigger,result); } if (result.type) { if (pageTrigger == 24) showTypes = true; handleSuccess(report,result,showTypes); } else if (result.error) { report( [{msg:result.error, className:"jquery-console-message-error jquery-console-message-compile-error"}] ); notice('compile-error', "A compile-time error! "+ "It just means the expression wasn't quite right. " + "Try again.", 'prompt'); } else if (result.exception) { var err = limitsError(result.exception); report( [{msg:err, className:"jquery-console-message-error jquery-console-message-exception"}] ); if (err == result.exception) { notice('compile-error', "A run-time error! The expression was right but the"+ " result didn't make sense. Check your expression and try again.", 'prompt'); } } else if (result.internal) { report( [{msg:limitsError(result.internal), className:"jquery-console-message-error jquery-console-message-internal"}] ); } else if (result.bind) { report(); } else if (result.result) { if (result.expr.match(/^:modules/)) { report( [{msg:result.result.replace(/[\["\]]/g,'') .replace(/,/g,', '), className:"jquery-console-message-type"}]); } } }); }, charInsertTrigger:function(){ var t = notice('tellaboutreturn', "Hit Return when you're "+ "finished typing your expression."); if (t) tellAboutRet = t; return true; }, autofocus:true, promptHistory:true, historyPreserveColumn:true, welcomeMessage:'Type Haskell expressions in here.' }); controller.finishCommand = function() { controller.ajaxloader.remove(); $('.jquery-console-prompt :last').each(function(){ lastLine = controller.currentLine; if (!$(this).hasClass('prompt-done')) { $(this).addClass('prompt-done'); $(this).click(function(){ controller.promptText(controller.currentLine); }); } }); } makeGuidSamplesClickable(); var match = window.location.href.match(/#([0-9]+)$/); if (match) { pageTrigger = match[1]-1; setTutorialPage(undefined,match[1]-1); } var match = window.location.href.match(/\?input=([^&]+)/); if (match) { controller.promptText(urlDecode(match[1])); controller.inner.click(); controller.typer.consoleControl(13); } }); function urlDecode (encodedString) { var output = encodedString; var binVal, thisString; var myregexp = /(%[^%]{2})/; while ((match = myregexp.exec(output)) != null && match.length > 1 && match[1] != '') { binVal = parseInt(match[1].substr(1),16); thisString = String.fromCharCode(binVal); output = output.replace(match[1], thisString); } return output; } function makeGuidSamplesClickable() { $('.guide code').each(function(){ $(this).css('cursor','pointer'); $(this).attr('title','Click me to insert "' + $(this).text() + '" into the console.'); $(this).click(function(){ controller.promptText($(this).text()); controller.inner.click(); }); }); } String.prototype.trim = function() { return this.replace(/^[\t ]*(.*)[\t ]*$/,'$1'); }; //////////////////////////////////////////////////////////////////////// // Trigger console commands function libTrigger(line,report) { switch (line.trim()) { case 'help': { setTutorialPage(undefined,0); report(); pageTrigger = 0; return true; } case 'back': { if (pageTrigger > 0) { setTutorialPage(undefined,pageTrigger-1); pageTrigger--; report(); return true; } break; } case 'lessons': { var lessons = $('
    '); for (var i = 0; i < pages.length; i++) { if (pages[i].lesson) { lessons.append($('
  1. '). html('lesson'+pages[i].lesson+' - ' + pages[i].title)); } } var lessonsList = '

    Lessons

    ' + lessons.html(); tutorialGuide.animate({opacity:0,height:0},'fast',function(){ tutorialGuide.html(lessonsList); tutorialGuide.css({height:'auto'}); tutorialGuide.animate({opacity:1},'fast'); makeGuidSamplesClickable(); }); report(); return true; } default: { if (line.trim() == 'chat') { notice('irc', 'Enter your nick on the right hand side and hit Connect!', 'prompt'); report(); runWebchat(); return true; } var m = line.trim().match(/^link(.*)/); if (m) { var data; if (m[1]) data = m[1].trim(); else if (lastLine) data = lastLine; if (data) { var addr = '?input=' + encodeHex(data); report([{msg:'',className:'latest-link'}]); var link = $(''). text('link for ' + data).click(function(){ window.location.href = $(this).attr('href'); return false; }); $('.latest-link').html(link).removeClass('latest-link'); return true; } } var m = line.trim().match(/^step([0-9]+)/); if (m) { if ((m[1]*1) <= pages.length) { setTutorialPage(undefined,m[1]-1); report(); pageTrigger = m[1]-1; return true; } } var m = line.trim().match(/^lesson([0-9]+)/); if (m) { for (var i = 0; i < pages.length; i++) { if (pages[i].lesson == m[1]*1) { setTutorialPage(undefined,i); report(); pageTrigger = i; return true; } } } } }; }; //////////////////////////////////////////////////////////////////////// // Change the tutorial page function setTutorialPage(result,n) { if (pages[n]) { window.location.href = '#' + (1*n + 1); tutorialGuide.find('.lesson').remove(); tutorialGuide.animate({opacity:0,height:0},'fast',function(){ if (typeof(pages[n].guide) == 'function') tutorialGuide.html(pages[n].guide(result)); else tutorialGuide.html(pages[n].guide); var back = ''; if (pageTrigger>0) back = 'You\'re at step' + (n+1) + '. Type back to go back.'; else back = 'You\'re at step' + (n+1) + '. Type step' + (n+1) + ' to return here.'; if (true) tutorialGuide .append('
    ' + back + '
    ') .append('
    Lesson: ' + searchLessonBack(n) + '
    '); tutorialGuide.css({height:'auto'}); tutorialGuide.animate({opacity:1},'fast'); makeGuidSamplesClickable(); }); } }; function searchLessonBack(page) { for (var i = page; i >= 0; i--) { if (pages[i].lesson) return pages[i].lesson; } return "1"; } //////////////////////////////////////////////////////////////////////// // Trigger a page according to a result function triggerTutorialPage(n,result) { n++; if (pages[n] && (typeof (pages[n].trigger) == 'function') && pages[n].trigger(result)) { pageTrigger++; setTutorialPage(result,n); } }; //////////////////////////////////////////////////////////////////////// // Trigger various libraries after JSONRPC returned function handleSuccess(report,result,showType) { if (result.type.match(/^Graphics\.Raphael\.Raphael[\r\n ]/)) { runRaphael(result.result); report(); } else { if (result.result) { var type = []; if (showType) { type = [{msg:':: ' + result.type, className:"jquery-console-message-type"}]; } report( [{msg:'=> ' + result.result, className:"jquery-console-message-value"}].concat(type) ); } else { report( [{msg:':: ' + result.type, className:"jquery-console-message-type"}] ); } } }; //////////////////////////////////////////////////////////////////////// // Raphael support function runRaphael(expr) { raphaelPaper.clear(); $('#raphael').parent().parent().slideDown(function(){ var exprs = expr.split(/\n/g); for (var i = 0; i < exprs.length; i++) raphaelRunExpr(exprs[i]); }); } function raphaelRunExpr(expr) { var expr = expr.split(/ /g); switch (expr[0]) { case 'new': { switch (expr[2]) { case 'circle': { var x = expr[3], y = expr[4], radius = expr[5]; var circle = raphaelPaper.circle(x*1,y*1,radius*1); circle.attr("fill", "#7360a4"); break; } } } } } function notice(name,msg,style) { if (opera()) return; if (!notices[name]) { notices[name] = name; return controller.notice(msg,style); } } function limitsError(str) { if (str == "Terminated!") { notice('terminated', "This error means it took to long to work" + " out on the server.", 'fadeout'); return "Terminated!"; } else if (str == "Time limit exceeded.") { notice('exceeded', "This error means it took to long to work out on the server. " + "Try again.", 'fadeout'); return "Terminated! Try again."; } return str; } })(jQuery);