// 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; i To kick off let's try some maths out. Up there you can"
+ " type in Haskell expressions. Try this out: Well done, you typed it perfect! You got back the number'+
' Let's try something completely different."+
" Type in your name like this:" +
' 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 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: 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: You can do stuff with lists. Maybe you want the lottery "+
"numbers sorted in the right order, try this: " +
" Congratulations, you just used a function."+
" They're how you get things done in Haskell." +
" As you might've guessed, we got back Ever wanted an evil twin nemesis? Me too. "+
"Luckily, you can sort lists of characters, or "+
"strings" +
", in the same way as numbers! Watch out for "+nemesis+"! You should keep their credentials for the police. My nemesis is 28 years of age: "+
" 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 " +
" Good job! You got the age back from the tuple! Didn't " +
" even break a sweat, did you? The Time to take a rest and see what you learned: 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: You just bound a variable. " +
"That is, you bound It's like this: ' + rmsg(['Learning By Numbers','Music is Math','Back to Basics'])
+ '
'
+ "5 + 7' + rmsg(['Your first Haskell expression',
"First Time's a Charm"]) + '
'
+ '' + result.result + '. Just what '+who+' wanted. '
+ ""chris"' + rmsg(['Types of values',"What's in a name?"]) +
'
'
+ 'chat to start it."+
" You will join the official IRC channel of the Haskell community![42,13,22]' + rmsg(["Lesson 1 done already!"]) +
'
' +
""+
"
" +
"sort " + result.result + "' + rmsg(["We put the funk in function"]) +
'
' +
"" +
htmlEncode(result.result)
+ ".sort \"chris\"' +
rmsg(["Tuples, because sometimes one value ain't enough!"]) +
'
' +
"(28,\"chirs\")' +
rmsg(["We'll keep them safe, sir."]) +
'
' +
"
" +
"(1,\"hats\",23/35)(\"Shaggy\",\"Daphnie\",\"Velma\")" + 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!"]) +
'
' +
"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!"+
"
" +
"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
" +
"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.let var = expression in bodyin 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 "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 '
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 '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 'Let's have a gander at what you learned:
" + "'a' : [], : is really just " +
" another function, just clever looking.(:) when " +
" you talk about them.['a','b'] can just be written " +
"\"ab\". Much easier!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 "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):
" + "map (*99) [1..10]map (/5) [13,24,52,42]filter (>5) [62,3,25,7,1,9]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 "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 "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 add1 x = x + 1 in add1 5let second x = snd x in second (3,4)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 "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:
" + "let add1 x = x + 1 in map add1 [1,5,7]let take5s = filter (==5) in take5s [1,5,2,5,3,5]let take5s = filter (==5) in map take5s [[1,5],[5],[1,1]]Did you get back what you expected?
" + "One more example for text; how do you upcase a letter?
" + "toUpper 'a'
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"
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:
" + "map take other functions as parameters.(+1), (>5) and "+
"square can be passed to other functions.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!
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 "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 "Try to get the 'a' value from this value using pattern matching:
(10,\"abc\")
Spoiler: let (_,(a:_)) = (10,\"abc\") in a
Wizard! I think you've got pattern-matching down.
" + "If you're still a bit unsure, here are some other things you can try:
" + "let _:_:c:_ = \"abcd\" in clet [a,b,c] = \"cat\" in (a,b,c)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 "That was easy, right?
" + "Let's go over what you've learned in this lesson:
" + "Now we get to the Deep Ones. Types!
" + "Consider the following value: 'a'
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 "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 "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:
" + ":t words:t unwords:t True:t notThe 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 "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:
" + "True :: Bool.a -> b.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 "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
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
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 "'Lo bob! You've already used the map function loads. I wonder if you can guess its type?
map :: (spoiler)(a -> b) -> [a] -> [b]
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 "Wow! You're doing so great! Have a look at what you know now!
" + "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.
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 = $('lesson'+pages[i].lesson+' - ' +
pages[i].title));
}
}
var lessonsList = '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('