https://wiki.haskell.org/api.php?action=feedcontributions&user=Hyiltiz&feedformat=atomHaskellWiki - User contributions [en]2020-12-05T03:43:52ZUser contributionsMediaWiki 1.27.4https://wiki.haskell.org/index.php?title=IRC_channel&diff=63393IRC channel2020-08-21T17:42:17Z<p>Hyiltiz: /* lambdabot */ document @where paste</p>
<hr />
<div>Internet Relay Chat is a worldwide text chat service with many thousands<br />
of users among various irc networks.<br />
<br />
The Freenode IRC network hosts the very large #haskell channel, and we've had<br />
up to 1046<br />
concurrent users, making the channel consistently<br />
[https://netsplit.de/channels/details.php?room=%23haskell&net=freenode one of the most popular]<br />
of the thousands of channels on freenode. One famous<br />
resident is [[Lambdabot]], another is [http://hpaste.org hpaste] (see<br />
the [[#Bots|Bots]] section below).<br />
<br />
The IRC channel can be an excellent place to learn more about Haskell,<br />
and to just keep in the loop on new things in the Haskell world. Many<br />
new developments in the Haskell world first appear on the irc channel.<br />
<br />
Since 2009, the Haskell channel has grown large enough that we've split it in two parts:<br />
<br />
* #haskell, for all the usual things<br />
* #haskell-in-depth , for those seeking in depth, or more theoretical discussion<br />
<br />
As always, #haskell remains the primary place for new user questions.<br />
<br />
{| border="0" <br />
|+ '''#haskell visualized'''<br />
|-<br />
| [[Image:Haskell-current.png|thumb|The social graph, Jan 2008]]<br />
| [[Image:Irc-raw.png|thumb|Daily traffic since 2004]]<br />
|-<br />
| [[Image:Nick-activity.png|thumb|Growth]]<br />
| [[Image:Haskell-wordle-irc.png|thumb|Noun map]]<br />
|}<br />
<br />
== Getting there ==<br />
<br />
If you point your irc client to [irc://chat.freenode.net/haskell chat.freenode.net] and then join the #haskell channel, you'll be there. Alternately, you can try http://webchat.freenode.net/ which connects inside the browser.<br />
<br />
Example, using [http://www.irssi.org/ irssi]:<br />
<br />
$ irssi -c chat.freenode.net -n myname -w mypassword<br />
/join #haskell<br />
<br />
Tip, if you're using Emacs to edit your Haskell sources then why not use it to chat about Haskell? Check out [http://www.emacswiki.org/cgi-bin/wiki/EmacsIRCClient ERC], The Emacs IRC client. Invoke it like this and follow the commands:<br />
<br />
M-x erc-select<br />
...<br />
/join #haskell<br />
<br />
[[Image:Irc--haskell-screenshot.png|frame|A screenshot of an irssi session in #haskell]]<br />
<br />
== Principles ==<br />
<br />
The #haskell channel is a very friendly, welcoming place to hang out,<br />
teach and learn. The goal of #haskell is to encourage learning and<br />
discussion of Haskell, functional programming, and programming in<br />
general. As part of this we welcome newbies, and encourage teaching of<br />
the language.<br />
<br />
Part of the #haskell success comes from the fact that the community<br />
is quite tight knit &mdash; we know each other &mdash; it's not just a homework<br />
channel. As a result, many collaborative projects have arisen between<br />
Haskell irc channel citizens.<br />
<br />
To maintain the friendly, open culture, the following is required:<br />
<br />
* Low to zero tolerance for ridiculing questions. Insulting new users is unacceptable. New Haskell users should feel entirely comfortable asking questions.<br />
<br />
* Helpful answers should be encouraged with <code>name++</code> karma points, in public, as a reward for providing a good answer.<br />
<br />
* Avoid getting frustrated by negative comments and ambiguous questions. Approach them by asking for details (i.e. [http://en.wikipedia.org/wiki/Socratic_method Socratic questioning]), rather than challenging the competence of the writer (ad hominem). As the channel grows, we see a diverse range of people with different programming backgrounds getting accustomed to Haskell. Be patient and take satisfaction from spreading knowledge.<br />
<br />
== History ==<br />
<br />
The #haskell channel appeared in the late 90s, and really got going<br />
in early 2001, with the help of Shae Erisson (aka shapr).<br />
<br />
== Related channels ==<br />
<br />
In addition to the main Haskell channel there are also:<br />
<br />
=== Language/Country specific ===<br />
<br />
The Freenode staff have asked us to consolidate language channels into the "#haskell-" namespace rather than have them continue on in the "#haskell." namespace. Eventually the language channels below listed with "#haskell." will have to move.<br />
<br />
{| border="1" cellspacing="0" cellpadding="5"<br />
! Channel<br />
! Purpose<br />
|-<br />
| style="width: 20%;" | #haskell-br<br />
| Brazilian Portuguese (pt_BR) speakers<br />
|-<br />
| #haskell.cz<br />
| Czech speakers (UTF-8)<br />
|- <br />
| #haskell.de<br />
| German speakers<br />
|-<br />
| #haskell.dut<br />
| Dutch speakers<br />
|-<br />
| #haskell.es<br />
| Spanish speakers<br />
|-<br />
| #haskell.fi<br />
| Finnish speakers<br />
|-<br />
| #haskell-fr<br />
| French speakers (note the hyphen! in the channel name)<br />
|-<br />
| #haskell.hr<br />
| Croatian speakers<br />
|-<br />
| #haskell-id<br />
| Indonesian speakers (note the hyphen! in the channel name)<br />
|-<br />
| #haskell-it <br />
| Italian speakers (note the hyphen! in the channel name)<br />
|-<br />
| #haskell.jp <br />
| Japanese speakers<br />
|-<br />
| #haskell.scandinavian<br />
| Scandinavian speakers<br />
|-<br />
| #haskell-kr<br />
| Korean speakers<br />
|-<br />
| #haskell.no <br />
| Norwegian speakers<br />
|-<br />
| #haskell.pt<br />
| Portuguese speakers<br />
|-<br />
| #haskell-pl<br />
| Polish speakers<br />
|-<br />
| #haskell.ru <br />
| Russian speakers. Seems that most of them migrated to Jabber conference (haskell@conference.jabber.ru).<br />
|-<br />
| #haskell_ru <br />
| Russian speakers again, in UTF-8. For those, who prefer good ol' IRC channel with a lambdabot.<br />
|-<br />
| #haskell-ro<br />
| Romanian speakers.<br />
|-<br />
| #haskell.se <br />
| Swedish speakers<br />
|-<br />
| #haskell.tw<br />
| Chinese speakers (mainly in Taiwan)<br />
|-<br />
| #haskell.vn<br />
| Vietnamese speakers<br />
|-<br />
| #chicagohaskell<br />
| [http://chicagohaskell.com Chicago Haskell] programmers group<br />
|}<br />
<br />
=== Platform-specific ===<br />
{| border="1" cellspacing="0" cellpadding="5"<br />
! Channel<br />
! Purpose<br />
|-<br />
| style="width: 20%;" | #haskell-beginners<br />
| Haskell people focused on teaching and learning Haskell, not just beginners.<br />
|-<br />
| #haskell-offtopic<br />
| Haskell people talking about anything except Haskell itself (no TLS required)<br />
|-<br />
| #haskell-blah <br />
| Haskell people talking about anything except Haskell itself (TLS required)<br />
|-<br />
| #haskell-game<br />
| The hub for Haskell-based [[Game Development|game development]]<br />
|-<br />
| #haskell-in-depth<br />
| slower paced discussion of use, theory, implementation etc with no monad tutorials!<br />
|-<br />
| #haskell-iphone<br />
| Haskell-based [[iPhone]] development<br />
|-<br />
| #haskell-apple<br />
| projects that target iOS or OS X using Haskell. <br />
|-<br />
| #haskell-lisp<br />
| [[Haskell Lisp]] - projects that are creating Lisps written in Haskell, or Haskell implementations written in Lisps. <br />
|-<br />
| #haskell-llvm<br />
| For projects using Haskell and LLVM<br />
|-<br />
| #haskell-overflow<br />
| Overflow conversations<br />
|-<br />
| #haskell-web<br />
| Friendly, practical discussion of haskell web app/framework/server development<br />
|-<br />
| #haskell-robotics<br />
| Discussion about the use of Haskell for robotics applications.<br />
|-<br />
| #arch-haskell <br />
| [[Arch Linux]]/ specific Haskell conversations<br />
|-<br />
| #fedora-haskell<br />
| [https://fedoraproject.org/wiki/Haskell Fedora] Haskell SIG<br />
|-<br />
| #gentoo-haskell <br />
| [[Gentoo]]/Linux specific Haskell conversations<br />
|}<br />
<br />
=== Projects using haskell ===<br />
{| border="1" cellspacing="0" cellpadding="5"<br />
! Channel <br />
! Purpose<br />
|-<br />
| style="width: 20%;" | #darcs <br />
| [[Darcs]] revision control system<br />
|-<br />
| #diagrams<br />
| [[Diagrams]] EDSL<br />
|-<br />
| #hackage<br />
| Haskell's software distribution infrastructure<br />
|-<br />
| #haskell-lens<br />
| [[Lens]] discussions<br />
|-<br />
| #haskell-stack<br />
| [https://github.com/commercialhaskell/stack/tree/master/doc Stack] discussions<br />
|-<br />
| #happs<br />
| [http://happstack.com Happstack] web framework<br />
|-<br />
| #hledger<br />
| [http://hledger.org hledger] accounting tools and library<br />
|-<br />
| #leksah<br />
| [http://leksah.org Leksah] IDE for Haskell development<br />
|-<br />
| #perl6 <br />
| [http://www.pugscode.org Perl 6] development (plenty of Haskell chat there too)<br />
|-<br />
| #snowdrift <br />
| [https://snowdrift.coop Snowdrift.coop] Yesod-based web platform for funding free/libre/open works, welcomes Haskell volunteer devs including beginners<br />
|-<br />
| #snapframework<br />
| [http://snapframework.com/ Snap] web framework<br />
|-<br />
| #xmonad<br />
| [http://xmonad.org Xmonad] tiling window manager<br />
|-<br />
| #yesod<br />
| [http://yesodweb.com Yesod] web framework<br />
|-<br />
| #yampa<br />
| [https://wiki.haskell.org/Yampa Yampa] Arrowized FRP<br />
|}<br />
<br />
== Logs ==<br />
<br />
'''Logs''' are kept at http://tunes.org/~nef/logs/haskell/ and can be searched at http://ircbrowse.net/browse/haskell<br />
<br />
<!-- anywhere else? ircbrowse.com is a goner, apparently --><br />
<br />
== Bots ==<br />
<br />
There are various bots on the channel. Their names and usage are described here.<br />
<br />
=== lambdabot ===<br />
<br />
[[Lambdabot]] is both the name of a software package and a bot on the channel. It provides many useful services for visitors to the IRC channel. It is available as a haskell package and can be integrated into ghci. Details on the software are found on a [[Lambdabot|separate wiki page]].<br />
<br />
Here is its interface for the IRC user:<br />
<br />
lambdabot's commands are prepended by a '@' sign.<br />
<br />
{| border="1" cellspacing="0" cellpadding="5"<br />
! Command<br />
! Usage<br />
|-<br />
| @help<br />
| display help to other commands, but help text is not available for all commands.<br />
|-<br />
| @where paste<br />
| shows instructions for online pastebin service.<br />
|-<br />
| @type EXPR or ':t' EXPR<br />
| shows the type of an expression<br />
|-<br />
| @kind TYPECONSTRUCTOR<br />
| shows the kind of a type constructor<br />
|-<br />
| @run EXPR or '>' EXPR<br />
| evaluates EXPR<br />
|-<br />
| @pl FUNCTION<br />
| shows a [[pointfree]] version of FUNCTION<br />
|-<br />
| @pointful FUNCTION or '@unpl' FUNCTION<br />
| shows a 'pointful' version of FUNCTION<br />
|-<br />
| @tell <nick> <msg> -- same as @ask<br />
| Next time <nick> speaks in channel they will be notified they have a message pending and how to receive it.<br />
|}<br />
<br />
=== yahb ===<br />
If lambdabot doesn't cut it for you, there is a bot called yahb which runs your request in an actual GHCi prompt, so you can use IO.<br />
<br />
Try e.g. <tt>% readFile "/proc/self/environ"</tt><br />
<br />
=== hackage ===<br />
The hackage bot provides real-time notifications of new package uploads to [http://hackage.haskell.org Hackage].<br />
<br />
== Locations ==<br />
<br />
To get an overview of where everybody on the channel might<br />
be, physically, please visit [[Haskell user locations]].<br />
<br />
<br />
[[Category:Community]]</div>Hyiltizhttps://wiki.haskell.org/index.php?title=IRC_channel&diff=63347IRC channel2020-07-22T04:31:04Z<p>Hyiltiz: /* preflex */ doesnt exist?</p>
<hr />
<div>Internet Relay Chat is a worldwide text chat service with many thousands<br />
of users among various irc networks.<br />
<br />
The Freenode IRC network hosts the very large #haskell channel, and we've had<br />
up to 1046<br />
concurrent users, making the channel consistently<br />
[https://netsplit.de/channels/details.php?room=%23haskell&net=freenode one of the most popular]<br />
of the thousands of channels on freenode. One famous<br />
resident is [[Lambdabot]], another is [http://hpaste.org hpaste] (see<br />
the [[#Bots|Bots]] section below).<br />
<br />
The IRC channel can be an excellent place to learn more about Haskell,<br />
and to just keep in the loop on new things in the Haskell world. Many<br />
new developments in the Haskell world first appear on the irc channel.<br />
<br />
Since 2009, the Haskell channel has grown large enough that we've split it in two parts:<br />
<br />
* #haskell, for all the usual things<br />
* #haskell-in-depth , for those seeking in depth, or more theoretical discussion<br />
<br />
As always, #haskell remains the primary place for new user questions.<br />
<br />
{| border="0" align="right"<br />
|+ '''#haskell visualized'''<br />
|-<br />
| [[Image:Haskell-current.png|thumb|The social graph, Jan 2008]]<br />
| [[Image:Irc-raw.png|thumb|Daily traffic since 2004]]<br />
|-<br />
| [[Image:Nick-activity.png|thumb|Growth]]<br />
| [[Image:Haskell-wordle-irc.png|thumb|Noun map]]<br />
|}<br />
<br />
== Getting there ==<br />
<br />
If you point your irc client to [irc://chat.freenode.net/haskell chat.freenode.net] and then join the #haskell channel, you'll be there. Alternately, you can try http://webchat.freenode.net/ which connects inside the browser.<br />
<br />
Example, using [http://www.irssi.org/ irssi]:<br />
<br />
$ irssi -c chat.freenode.net -n myname -w mypassword<br />
/join #haskell<br />
<br />
Tip, if you're using Emacs to edit your Haskell sources then why not use it to chat about Haskell? Check out [http://www.emacswiki.org/cgi-bin/wiki/EmacsIRCClient ERC], The Emacs IRC client. Invoke it like this and follow the commands:<br />
<br />
M-x erc-select<br />
...<br />
/join #haskell<br />
<br />
[[Image:Irc--haskell-screenshot.png|frame|A screenshot of an irssi session in #haskell]]<br />
<br />
== Principles ==<br />
<br />
The #haskell channel is a very friendly, welcoming place to hang out,<br />
teach and learn. The goal of #haskell is to encourage learning and<br />
discussion of Haskell, functional programming, and programming in<br />
general. As part of this we welcome newbies, and encourage teaching of<br />
the language.<br />
<br />
Part of the #haskell success comes from the fact that the community<br />
is quite tight knit &mdash; we know each other &mdash; it's not just a homework<br />
channel. As a result, many collaborative projects have arisen between<br />
Haskell irc channel citizens.<br />
<br />
To maintain the friendly, open culture, the following is required:<br />
<br />
* Low to zero tolerance for ridiculing questions. Insulting new users is unacceptable. New Haskell users should feel entirely comfortable asking questions.<br />
<br />
* Helpful answers should be encouraged with <code>name++</code> karma points, in public, as a reward for providing a good answer.<br />
<br />
* Avoid getting frustrated by negative comments and ambiguous questions. Approach them by asking for details (i.e. [http://en.wikipedia.org/wiki/Socratic_method Socratic questioning]), rather than challenging the competence of the writer (ad hominem). As the channel grows, we see a diverse range of people with different programming backgrounds getting accustomed to Haskell. Be patient and take satisfaction from spreading knowledge.<br />
<br />
== History ==<br />
<br />
The #haskell channel appeared in the late 90s, and really got going<br />
in early 2001, with the help of Shae Erisson (aka shapr).<br />
<br />
== Related channels ==<br />
<br />
In addition to the main Haskell channel there are also:<br />
<br />
=== Language/Country specific ===<br />
<br />
The Freenode staff have asked us to consolidate language channels into the "#haskell-" namespace rather than have them continue on in the "#haskell." namespace. Eventually the language channels below listed with "#haskell." will have to move.<br />
<br />
{| border="1" cellspacing="0" cellpadding="5" align="left"<br />
! Channel<br />
! Purpose<br />
|-<br />
| style="width: 20%;" | #haskell-br<br />
| Brazilian Portuguese (pt_BR) speakers<br />
|-<br />
| #haskell.cz<br />
| Czech speakers (UTF-8)<br />
|- <br />
| #haskell.de<br />
| German speakers<br />
|-<br />
| #haskell.dut<br />
| Dutch speakers<br />
|-<br />
| #haskell.es<br />
| Spanish speakers<br />
|-<br />
| #haskell.fi<br />
| Finnish speakers<br />
|-<br />
| #haskell-fr<br />
| French speakers (note the hyphen! in the channel name)<br />
|-<br />
| #haskell.hr<br />
| Croatian speakers<br />
|-<br />
| #haskell-id<br />
| Indonesian speakers (note the hyphen! in the channel name)<br />
|-<br />
| #haskell-it <br />
| Italian speakers (note the hyphen! in the channel name)<br />
|-<br />
| #haskell.jp <br />
| Japanese speakers<br />
|-<br />
| #haskell.scandinavian<br />
| Scandinavian speakers<br />
|-<br />
| #haskell-kr<br />
| Korean speakers<br />
|-<br />
| #haskell.no <br />
| Norwegian speakers<br />
|-<br />
| #haskell.pt<br />
| Portuguese speakers<br />
|-<br />
| #haskell-pl<br />
| Polish speakers<br />
|-<br />
| #haskell.ru <br />
| Russian speakers. Seems that most of them migrated to Jabber conference (haskell@conference.jabber.ru).<br />
|-<br />
| #haskell_ru <br />
| Russian speakers again, in UTF-8. For those, who prefer good ol' IRC channel with a lambdabot.<br />
|-<br />
| #haskell-ro<br />
| Romanian speakers.<br />
|-<br />
| #haskell.se <br />
| Swedish speakers<br />
|-<br />
| #haskell.tw<br />
| Chinese speakers (mainly in Taiwan)<br />
|-<br />
| #haskell.vn<br />
| Vietnamese speakers<br />
|-<br />
| #chicagohaskell<br />
| [http://chicagohaskell.com Chicago Haskell] programmers group<br />
|}<br />
<br />
=== Platform-specific ===<br />
{| border="1" cellspacing="0" cellpadding="5" align="left"<br />
! Channel<br />
! Purpose<br />
|-<br />
| style="width: 20%;" | #haskell-beginners<br />
| Haskell people focused on teaching and learning Haskell, not just beginners.<br />
|-<br />
| #haskell-offtopic<br />
| Haskell people talking about anything except Haskell itself (no TLS required)<br />
|-<br />
| #haskell-blah <br />
| Haskell people talking about anything except Haskell itself (TLS required)<br />
|-<br />
| #haskell-game<br />
| The hub for Haskell-based [[Game Development|game development]]<br />
|-<br />
| #haskell-in-depth<br />
| slower paced discussion of use, theory, implementation etc with no monad tutorials!<br />
|-<br />
| #haskell-iphone<br />
| Haskell-based [[iPhone]] development<br />
|-<br />
| #haskell-apple<br />
| projects that target iOS or OS X using Haskell. <br />
|-<br />
| #haskell-lisp<br />
| [[Haskell Lisp]] - projects that are creating Lisps written in Haskell, or Haskell implementations written in Lisps. <br />
|-<br />
| #haskell-llvm<br />
| For projects using Haskell and LLVM<br />
|-<br />
| #haskell-overflow<br />
| Overflow conversations<br />
|-<br />
| #haskell-web<br />
| Friendly, practical discussion of haskell web app/framework/server development<br />
|-<br />
| #haskell-robotics<br />
| Discussion about the use of Haskell for robotics applications.<br />
|-<br />
| #arch-haskell <br />
| [[Arch Linux]]/ specific Haskell conversations<br />
|-<br />
| #fedora-haskell<br />
| [https://fedoraproject.org/wiki/Haskell Fedora] Haskell SIG<br />
|-<br />
| #gentoo-haskell <br />
| [[Gentoo]]/Linux specific Haskell conversations<br />
|}<br />
<br />
=== Projects using haskell ===<br />
{| border="1" cellspacing="0" cellpadding="5" align="left"<br />
! Channel <br />
! Purpose<br />
|-<br />
| style="width: 20%;" | #darcs <br />
| [[Darcs]] revision control system<br />
|-<br />
| #diagrams<br />
| [[Diagrams]] EDSL<br />
|-<br />
| #hackage<br />
| Haskell's software distribution infrastructure<br />
|-<br />
| #haskell-lens<br />
| [[Lens]] discussions<br />
|-<br />
| #haskell-stack<br />
| [https://github.com/commercialhaskell/stack/tree/master/doc Stack] discussions<br />
|-<br />
| #happs<br />
| [http://happstack.com Happstack] web framework<br />
|-<br />
| #hledger<br />
| [http://hledger.org hledger] accounting tools and library<br />
|-<br />
| #leksah<br />
| [http://leksah.org Leksah] IDE for Haskell development<br />
|-<br />
| #perl6 <br />
| [http://www.pugscode.org Perl 6] development (plenty of Haskell chat there too)<br />
|-<br />
| #snowdrift <br />
| [https://snowdrift.coop Snowdrift.coop] Yesod-based web platform for funding free/libre/open works, welcomes Haskell volunteer devs including beginners<br />
|-<br />
| #snapframework<br />
| [http://snapframework.com/ Snap] web framework<br />
|-<br />
| #xmonad<br />
| [http://xmonad.org Xmonad] tiling window manager<br />
|-<br />
| #yesod<br />
| [http://yesodweb.com Yesod] web framework<br />
|-<br />
| #yampa<br />
| [https://wiki.haskell.org/Yampa Yampa] Arrowized FRP<br />
|}<br />
<br />
== Logs ==<br />
<br />
'''Logs''' are kept at http://tunes.org/~nef/logs/haskell/ and can be searched at http://ircbrowse.net/browse/haskell<br />
<br />
<!-- anywhere else? ircbrowse.com is a goner, apparently --><br />
<br />
== Bots ==<br />
<br />
There are various bots on the channel. Their names and usage are described here.<br />
<br />
=== lambdabot ===<br />
<br />
[[Lambdabot]] is both the name of a software package and a bot on the channel. It provides many useful services for visitors to the IRC channel. It is available as a haskell package and can be integrated into ghci. Details on the software are found on a [[Lambdabot|separate wiki page]].<br />
<br />
Here is its interface for the IRC user:<br />
<br />
lambdabot's commands are prepended by a '@' sign.<br />
<br />
{| border="1" cellspacing="0" cellpadding="5" align="center"<br />
! Command<br />
! Usage<br />
|-<br />
| @help<br />
| display help to other commands, but help text is not available for all commands.<br />
|-<br />
| @type EXPR or ':t' EXPR<br />
| shows the type of an expression<br />
|-<br />
| @kind TYPECONSTRUCTOR<br />
| shows the kind of a type constructor<br />
|-<br />
| @run EXPR or '>' EXPR<br />
| evaluates EXPR<br />
|-<br />
| @pl FUNCTION<br />
| shows a [[pointfree]] version of FUNCTION<br />
|-<br />
| @pointful FUNCTION or '@unpl' FUNCTION<br />
| shows a 'pointful' version of FUNCTION<br />
|-<br />
| @tell <nick> <msg> -- same as @ask<br />
| Next time <nick> speaks in channel they will be notified they have a message pending and how to receive it.<br />
|}<br />
<br />
=== preflex ===<br />
<br />
is the name of a lambdabot with more commands/plugins enabled. It is run by ?? To talk to preflex, write <tt>preflex: command ARGS</tt> Desn't exist as of July 22, 2020.<br />
<br />
{| border="1" cellspacing="0" cellpadding="5" align="center"<br />
! Command<br />
! Usage<br />
|-<br />
| help COMMAND<br />
| displays help to other commands.<br />
|-<br />
| list<br />
| lists all plugins with their commands<br />
|-<br />
| NICK++ / NICK--<br />
| in/decrements the karma of NICK.<br />
|-<br />
| karma NICK<br />
| shows the karma of NICK<br />
|-<br />
| seen NICK<br />
| shows information about the last message of a user<br />
|-<br />
| tell / ask<br />
| sends NICK MSG a message when she becomes active.<br />
|-<br />
| xseen<br />
| ''see 'seen' ?? any difference ?''<br />
|-<br />
| quote NICK<br />
| prints a random quote of NICK<br />
|-<br />
| remember NAME QUOTE<br />
| associates NAME with quote. can be accessed by 'quote'<br />
|-<br />
| ...<br />
| ...<br />
|}<br />
<br />
=== hackage ===<br />
The hackage bot provides real-time notifications of new package uploads to [http://hackage.haskell.org Hackage].<br />
<br />
== Locations ==<br />
<br />
To get an overview of where everybody on the channel might<br />
be, physically, please visit [[Haskell user locations]].<br />
<br />
<br />
[[Category:Community]]</div>Hyiltizhttps://wiki.haskell.org/index.php?title=Tying_the_Knot&diff=63346Tying the Knot2020-07-22T00:33:05Z<p>Hyiltiz: (n+1) in pattern matching doesn't compile with GHC 8.6.5</p>
<hr />
<div>In a language like Haskell, where Lists are defined as <hask>Nil | Cons a (List a)</hask>, creating data structures like cyclic or doubly linked lists seems impossible. However, this is not the case: laziness allows for such definitions, and the procedure of doing so is called ''tying the knot''. The simplest example:<br />
<br />
<haskell><br />
cyclic = let x = 0 : y<br />
y = 1 : x<br />
in x<br />
</haskell><br />
<br />
This creates the cyclic list consisting of 0 and 1. It is important to stress that this procedure allocates only two numbers - 0 and 1 - in memory, making this a truly cyclic list.<br />
<br />
The knot analogy stems from the fact that we produce two open-ended objects, and then link their ends together. Evaluation of the above therefore looks something like<br />
<br />
<haskell><br />
cyclic<br />
= x<br />
= 0 : y<br />
= 0 : 1 : x -- Knot! Back to the beginning.<br />
= 0 : 1 : 0 : y<br />
= -- etc.<br />
</haskell><br />
<br />
<br />
== Overview ==<br />
<br />
This example illustrates different ways to define recursive data structures.<br />
To demonstrate the different techniques we show how to solve the same problem---writing an interpreter for a simple programming language---in three different ways. This is a nice example because, (i) it is interesting, (ii) the abstract syntax of the language contains mutually recursive structures, and (iii) the interpreter illustrates how to work with the recursive structures.<br />
<br />
(It would be useful to have some more text describing the examples.)<br />
<br />
== Download the files ==<br />
* [[Media:Interp1.lhs|Direct Recursion]]<br />
* [[Media:Interp2.lhs|Tying the Knot]]<br />
* [[Media:Interp3.lhs|Tying the Knot with GADTs]]<br />
<br />
== Other Examples ==<br />
<br />
* [http://twan.home.fmf.nl/blog/haskell/Knuth-Morris-Pratt-in-Haskell.details Knuth-Morris-Pratt algorithm for substring matching]<br />
<br />
== Migrated from the old wiki ==<br />
<br />
How to build a cyclic data structure.<br />
<br />
At first, the lack of pointer-updating operations in Haskell makes it seem that building cyclic structures (circular lists, graphs, etc) is impossible. This is not the case, thanks to the ability to define data using recursive equations. Here is a little trick called `tying the knot'.<br />
<br />
Remember that Haskell is a lazy language. A consequence of this is that while you are building the node, you can set the children to the final values straight away, even though you don't know them yet! It twists your brain a bit the first few times you do it, but it works fine.<br />
<br />
Here's an example. Say you want to build a circular, doubly-linked list, given a standard Haskell list as input. The back pointers are easy, but what about the forward ones?<br />
<haskell><br />
data DList a = DLNode (DList a) a (DList a)<br />
<br />
mkDList :: [a] -> DList a<br />
<br />
mkDList [] = error "must have at least one element"<br />
mkDList xs = let (first,last) = go last xs first<br />
in first<br />
<br />
where go :: DList a -> [a] -> DList a -> (DList a, DList a)<br />
go prev [] next = (next,prev)<br />
go prev (x:xs) next = let this = DLNode prev x rest<br />
(rest,last) = go this xs next<br />
in (this,last)<br />
<br />
takeF :: Integer -> DList a -> [a]<br />
takeF 0 _ = []<br />
takeF n (DLNode _ x next) = x : (takeF (n-1) next)<br />
<br />
takeR :: Show a => Integer -> DList a -> [a]<br />
takeR 0 _ = []<br />
takeR n (DLNode prev x _) = x : (takeR (n-1) prev)<br />
</haskell><br />
(takeF and takeR are simply to let you look at the results of mkDList: they take a specified number of elements, either forward or backward).<br />
<br />
The trickery takes place in `go'. `go' builds a segment of the list, given a pointer to the node off to the left of the segment and off to the right. Look at the second case of `go'. We build the first node of the segment, using the given prev pointer for the left link, and the node pointer we are *about* to compute in the next step for the right link.<br />
<br />
This goes on right the way through the segment. But how do we manage to create a *circular* list this way? How can we know right at the beginning what the pointer to the end of the list will be?<br />
<br />
Take a look at mkDList. Here, we simply take the (first,last) pointers we get from `go', and *pass them back in* as the next and prev pointers respectively, thus tying the knot. This all works because of lazy evaluation.<br />
<br />
Somehow the following seems more straightforward to me, though perhaps I'm missing the point here:<br />
<haskell><br />
data DList a = DLNode (DList a) a (DList a)<br />
<br />
rot :: Integer -> [a] -> [a]<br />
rot n xs | n < 0 = rot (n+1) ((last xs):(init xs))<br />
| n == 0 = xs<br />
| n > 0 = rot (n-1) (tail xs ++ [head xs])<br />
<br />
mkDList :: [a] -> DList a<br />
mkDList [] = error "Must have at least one element."<br />
mkDList xs = DLNode (mkDList $ rot (-1) xs) (head xs) (mkDList $ rot 1 xs)<br />
</haskell><br />
<br />
- CaleGibbard<br />
<br />
The problem with this is it won't make a truly cyclic data structure, rather it will constantly be generating the rest of the list. To see this use trace (in Debug.Trace for GHC) in mkDList (e.g. mkDList xs = trace "mkDList" $ ...) and then takeF 10 (mkDList "a"). Add a trace to mkDList or go or wherever you like in the other version and note the difference.<br />
<br />
-- DerekElkins<br />
<br />
Yeah, thanks, I see what you mean.<br />
<br />
This is so amazing that everybody should have seen it, so here's the trace. I put trace "\n--go{1/2}--" $ directly after the two go definitions:<br />
<br />
<haskell><br />
*Main> takeF 10 $ mkDList [1..3]<br />
<br />
--go2--<br />
[1<br />
--go2--<br />
,2<br />
--go2--<br />
,3<br />
--go1--<br />
,1,2,3,1,2,3,1]<br />
<br />
</haskell><br />
<br />
- CaleGibbard<br />
<br />
The above works for simple cases, but sometimes you need construct some very complex data structures, where the pattern of recursion is not known at compile time. If this is the case, may need to use an auxiliary dictionary data structure to help you tie your knots.<br />
<br />
Consider, for example, how you would implement deterministic finite automata (DFAs). One possibility is:<br />
<haskell><br />
type IndirectDfa a = (Int, [IndirectState a])<br />
<br />
data IndirectState a =<br />
IndirectState Bool [(a, Int)]<br />
</haskell><br />
That is, a DFA is a set of states, one of which is distinguished as being the "start state". Each state has a number of transitions which lead to other states, as well as a flag which specifies or not the state is final.<br />
<br />
This representation is fine for manipulation, but it's not as suitable for actually executing the DFA as it could be because we need to "look up" a state every time we make a transition. There are relatively cheap ways to implement this indirection, of course, but ideally we shouldn't have to pay much for it.<br />
<br />
What we really want is a recursive data structure:<br />
<br />
<haskell><br />
data DirectDfa a<br />
= DirectState Bool [(a, DirectDfa a)]<br />
</haskell><br />
Then we can just execute the DFA like this:<br />
<haskell><br />
runDfa :: (Eq a) => DirectDfa a -> [a] -> Bool<br />
runDfa (DirectState final trans) []<br />
= final<br />
runDfa (DirectState final trans) (x:xs)<br />
= case [ s | (x',s) <- trans, x == x' ] of<br />
[] -> False<br />
(s:_) -> runDfa s xs<br />
</haskell><br />
(Note: We're only optimising state lookup here, not deciding which transition to take. As an exercise, consider how you might optimise transitions. You may wish to use [[RunTimeCompilation]].)<br />
<br />
Turning the indirect recursion into direct recursion requires tying knots, and it's not immediately obvious how to do this by holding onto lazy pointers, because any state can potentially point to any other state (or, indeed, every other state).<br />
<br />
What we can do is introduce a dictionary data structure to hold the (lazily evaluated) new states, then introducing a recursive reference can be done with a a simple dictionary lookup. In principle, you could use any dictionary data structure (e.g. Map). However, in this case, the state numbers are dense integers, so it's probably easiest to use an array:<br />
<haskell><br />
indirectToDirect :: IndirectDfa a -> DirectDfa a<br />
indirectToDirect (start, states)<br />
= tieArray ! start<br />
where<br />
tieArray = array (0,length states - 1)<br />
[ (i,direct s) | (i,s) <- zip [0..] states ]<br />
<br />
direct (IndirectState final trans)<br />
= DirectState final [ (x, tieArray ! s) | (x,s) <- trans ]<br />
</haskell><br />
Note how similar this is to the technique of [[MemoisingCafs]]. In fact what we've done here is "memoised" the data structure, using something like [[HashConsing]].<br />
<br />
As noted previously, pretty much any dictionary data structure will do. Often you can even use structures with slow lookup (e.g. association lists). This is because fully lazy evaluation ensures that you only pay for each lookup the first time you use it; subsequent uses of the same part of the data structure are effectively free.<br />
<br />
-- AndrewBromage<br />
<br />
Transformations of cyclic graphs and the Credit Card Transform<br />
<br />
Cycles certainly make it difficult to transform graphs in a pure non-strict language. Cycles in a source graph require us to devise a way to mark traversed nodes -- however we cannot mutate nodes and cannot even compare nodes with a generic ('derived') equality operator. Cycles in a destination graph require us to keep track of the already constructed nodes so we can complete a cycle. An obvious solution is to use a state monad and IORefs. There is also a monad-less solution, which is less obvious: seemingly we cannot add a node to the dictionary of already constructed nodes until we have built the node. This fact means that we cannot use the updated dictionary when building the descendants of the node -- which need the updated dictionary to link back. The problem can be overcome however with a ''credit card transform'' (a.k.a. "buy now, pay later" transform). To avoid hitting the bottom, we just have to "pay" by the "due date".<br />
<br />
For illustration, we will consider the problem of printing out a non-deterministic finite automaton (NFA) and transforming it into a deterministic finite automaton (DFA). Both NFA and DFA are represented as cyclic graphs. The problem has been discussed on the Haskell/Haskell-Cafe mailing lists. The automata in question were to recognize strings over a binary alphabet.<br />
<br />
A state of an automaton over a binary alphabet is a data structure:<br />
<haskell><br />
data (Ord l,Show l) => FaState l =<br />
FaState {label :: l, acceptQ :: Bool,<br />
trans0:: [FaState l],<br />
trans1:: [FaState l]}<br />
</haskell><br />
whose fields have the obvious meaning. Label is used for printing out and comparing states. The flag acceptQ tells if the state is final. Since an FaState can generally represent a non-deterministic automaton, transitions are the ''lists'' of states.<br />
<br />
An automaton is then a list of starting states.<br />
<haskell><br />
type FinAu l = [FaState l]<br />
</haskell><br />
For example, an automaton equivalent to the regular expression "0*(0(0+1)*)*" could be defined as:<br />
<haskell><br />
dom18 = [one]<br />
where one = FaState 1 True [one,two] []<br />
two = FaState 2 True [two,one] [one,two]<br />
</haskell><br />
using the straightforward translation from a regular expression to an NFA.<br />
<br />
We would like to compare and print automata and their states:<br />
<haskell><br />
instance (Ord l,Show l) => Eq (FaState l) where<br />
(FaState l1 _ _ _) == (FaState l2 _ _ _) = l1 == l2<br />
</haskell><br />
<br />
Printing a FaState however poses a slight problem. For example, the state labeled '1' in the automaton dom18 refers to itself. If we blindly 'follow the links', we will loop forever. Therefore, we must keep track of the already printed states. We need a data structure for such an occurrence check, with the following obvious operations:<br />
<br />
<haskell><br />
class OCC occ where<br />
empty:: occ a<br />
seenp:: (Eq a) => a -> occ a -> Bool -- occurrence check predicate<br />
put:: a -> occ a -> occ a -- add an item<br />
</haskell><br />
<br />
In this article, we realize such a data structure as a list. In the future, we can pull in something fancier from the Edison collection:<br />
<br />
<haskell><br />
instance OCC [] where<br />
empty = []<br />
seenp = elem<br />
put = (:)<br />
</haskell><br />
<br />
We are now ready to print an automaton. To be more precise, we traverse the corresponding graph depth-first, pre-order, and keep track of the already printed states. A 'states_seen' datum accumulates the shown states, so we can be sure we print each state only once and thus avoid the looping.<br />
<br />
<haskell><br />
instance (Ord l,Show l) => Show (FaState l) where<br />
show state = "{@" ++ showstates [state] (empty::[FaState l]) "@}"<br />
where<br />
-- showstates worklist seen_states suffix<br />
showstates [] states_seen suffix = suffix<br />
showstates (st:rest) states_seen suffix<br />
| st `seenp` states_seen = showstates rest states_seen suffix<br />
showstates (st@(FaState l accept t0 t1):rest) states_seen suffix =<br />
showstate st<br />
$ showstates (t0++t1++rest) (st `put` states_seen) suffix<br />
<br />
showstate (FaState l accept t0 t1) suffix<br />
= "{State " ++ (show l) ++<br />
" " ++ (show accept) ++ " " ++ (show $ map label t0) ++<br />
" " ++ (show $ map label t1) ++ "}" ++ suffix<br />
</haskell><br />
<br />
Now,<br />
<br />
<haskell><br />
CCardFA> print dom18 -- prints as<br />
CCardFA> [{@{State 1 True [1,2] []}{State 2 True [2,1] [1,2]}@}]<br />
</haskell><br />
<br />
The acceptance function for our automata can be written as follows. The function takes the list of starting states and the string over the boolean alphabet. The function returns True if the string is accepted.<br />
<br />
<haskell><br />
finAuAcceptStringQ start_states str =<br />
any (\l -> acceptP l str) start_states<br />
where acceptP (FaState _ acceptQ _ _) [] = acceptQ<br />
acceptP (FaState _ _ t0 t1) (s:rest) =<br />
finAuAcceptStringQ (if s then t1 else t0) rest<br />
</haskell><br />
<br />
To test the automata, we can try<br />
<br />
<haskell><br />
test1= finAuAcceptStringQ dom18 $ map (>0) [0,1,0,1]<br />
test2= finAuAcceptStringQ dom18 $ map (>0) [1,1,0,1]<br />
test3= finAuAcceptStringQ dom18 [True]<br />
test4= finAuAcceptStringQ dom18 [False]<br />
</haskell><br />
<br />
We are now ready to write the NFA->DFA conversion, a determinization of an NFA. We implement the textbook algorithm of tracing set of NFA states. A state in the resulting DFA corresponds to a list of the NFA states. A DFA is generally a cyclic graph, often with cycles of length 1 (self-referenced nodes). To be able to "link back" as we build DFA states, we have to remember the already constructed states. We need a data structure, a dictionary of states:<br />
<br />
<haskell><br />
class StateDict sd where<br />
emptyd :: sd (l,FaState l)<br />
locate :: (Eq l) => l -> sd (l,FaState l) -> Maybe (FaState l)<br />
putd :: (l,FaState l) -> sd (l,FaState l) -> sd (l,FaState l)<br />
</haskell><br />
<br />
For now, we realize this dictionary as an associative list. If performance matters, we can use a fancier dictionary from the Edison<br />
<br />
<haskell><br />
instance StateDict [] where<br />
emptyd = []<br />
locate = lookup<br />
putd = (:)<br />
</haskell><br />
<br />
The work of the NFA->DFA conversion is done by the following function determinize_cc. The function takes a list of NFA states, the dictionary of the already built states, and returns a pair <hask>([dfa_state], updated_dictionary)</hask> where <hask>[dfa_state]</hask> is a singleton list.<br />
<br />
<haskell><br />
-- [nfa_state] -> dictionary_of_seen_states -><br />
-- ([dfa_state],updated_dictionary)<br />
-- [dfa_state] is a singleton list<br />
determinize_cc states converted_states =<br />
-- first, check the cache to see if the state has been built already<br />
case dfa_label `locate` converted_states of<br />
Nothing -> build_state<br />
Just dfa_state -> ([dfa_state],converted_states)<br />
where<br />
-- [NFA_labels] -> DFA_labels<br />
det_labels = sort . nub . map label<br />
dfa_label = det_labels states<br />
<br />
-- find out NFA-followers for [nfa_state] upon ingestion of 0 and 1<br />
(t0_followers,t1_followers) =<br />
foldr (\st (f0,f1) -> (trans0 st ++ f0, trans1 st ++ f1))<br />
([],[]) states<br />
acceptQ' = any acceptQ states<br />
<br />
-- really build the dfa state and return ([dfa_state],updated_cache)<br />
build_state = let<br />
-- note, the dfa_state is computed _below_<br />
converted_states1 = (dfa_label,dfa_state) `putd` converted_states<br />
(t0', converted_states2) =<br />
(determinize_cc t0_followers converted_states1)<br />
(t1', converted_states3) =<br />
(determinize_cc t1_followers converted_states2)<br />
dfa_state =<br />
(FaState dfa_label acceptQ' t0' t1')<br />
in ([dfa_state],converted_states3)<br />
</haskell><br />
The front end of the NFA->DFA transformer:<br />
<hask>finAuDeterminize states = fst $ determinize_cc states []</hask><br />
<br />
At the heart of the credit card transform is the phrase from the above code:<br />
<hask> converted_states1 = (dfa_label,dfa_state) `putd` converted_states</hask><br />
<br />
The phrase expresses the addition to the dictionary of the 'converted_states' of a 'dfa_state' that we haven't built yet. The computation of the 'dfa_state' is written 4 lines below the phrase in question. Because (,) is non-strict in its arguments and locate is non-strict in its result, we can get away with a mere promise to "pay". Note that the computation of the dfa_state needs t0' and t1', which in turn rely on 'converted_states1'. This fact shows that we can tie the knot by making a promise to compute a state, add this promise to the dictionary of the built states, and use the updated dictionary to build the descendants. Because Haskell is a non-strict language, we don't need to do anything special to make the promise. Every computation is Haskell is by default a promise.<br />
<br />
We can print the DFA for dom18 to see what we've got:<br />
<haskell><br />
CCardFA> finAuDeterminize dom18<br />
CCardFA>-- which shows<br />
CCardFA> [{@{State [1] True [[1,2]] [[]] }<br />
CCardFA> {State [1,2] True [[1,2]] [[1,2]]}<br />
CCardFA> {State [] False [[]] [[]] }@}]<br />
</haskell><br />
which is indeed a DFA (which happens to be minimal) recognizing (0+1)* - 1(0+1)*<br />
<br />
We can run the determinized FA using the same function finAuAcceptStringQ:<br />
<haskell><br />
test1' = finAuAcceptStringQ (finAuDeterminize dom18) $ map (>0) [0,1,0,1]<br />
test2' = finAuAcceptStringQ (finAuDeterminize dom18) $ map (>0) [1,1,0,1]<br />
</haskell><br />
<br />
The complete code for this example is in http://pobox.com/~oleg/ftp/Haskell/CCard-transform-DFA.lhs. Another example of tying a knot in the case of forward links, by using a fixed-point combinator, is discussed in http://www.mail-archive.com/haskell@haskell.org/msg10687.html.<br />
<br />
-- Oleg<br />
<br />
For a long time, I've had an issue with Oleg's reply to Hal Daume III, the "forward links" example. The problem is that it doesn't really exploit laziness or circular values. It's solution would work even in a strict language. It's simply a functional version of the standard approach: build the result with markers and patch it up afterwards. It is a fairly clever way of doing purely something that is typically done with references and mutable update, but it doesn't really address what Hal Daume III was after. Fixing Hal Daume's example so that it won't loop is relatively trivial; simply change the case to a let or equivalently use a lazy pattern match in the case. However, if that's all there was to it, I would've written this a long time ago.<br />
<br />
The problem is that it no longer gives you control of the error message or anyway to recover from it. With GHC's extensions to exception handling you could do it, but you'd have to put readDecisionTree in the IO monad to recover from it, and if you wanted better messages you'd have to put most of the parsing in the IO monad so that you could catch the error earlier and provide more information then rethrow. What's kept me is that I couldn't figure out a way to tie the knot when the environment had a type like, Either String [(String,DecisionTree)]. This is because it's impossible for this case; we decide whether to return Left "could not find subtree" or Right someValue and therefore whether the environment is Left or Right based on whether we could find the subtree in the environment. In effect, we need to lookup a value in an environment we may return to know whether to return it. Obviously this is a truly circular dependency. This made me think that Oleg's solution was as good as any other and better than some (actually, ironically Oleg's solution also uses a let instead of a case, however, there's nothing stopping it from being a case, but it still would provide no way to recover from it without effectively doing what is mentioned below). Recently, I've thought about this again and the solution is obvious and follows directly from the original definition modified to use let. It doesn't loop because only particular values in the lookup table fail, in fact, you might never know there was a variable lookup error if you didn't touch all of the tree. This translates directly into the environment having type [(String,Either String DecisionTree)]. There are several benefits to this approach compared to Oleg's: 1) it solves my original problem, you are now able to specify the error messages (Oleg's can do this), 2) it goes beyond that (and beyond Hal Daume's original "specification") and also allows you to recover from an error without resorting to the IO monad and/or extensions (Oleg's can't do this), 3) it does implicitly what Oleg's version does explicitly, 4) because of (3) it shares properly while Oleg's does not*, 5) both the environment and the returned value are made up of showable values, not opaque functions, 6) it requires less changes to the original code and is more localized than Oleg's solution; only the variable lookup and top-level function will need to change.<br />
<br />
To recover, all one needs to do is make sure all the values in the lookup table are Right values. If they aren't, there are various ways you could collect the information; there are also variations on how to combine error information and what to provide. Even without a correctness check, you can still provide better error messages for the erroneous thunks. A possible variation that loses some of the benefits, is to change the DecisionTree type (or have a different version, [[IndirectComposite]] comes to mind here) that has Either ErrorInfo ErrorDecisionTree subnodes, which will allow you to recover at any time (though, if you want to make a normal DecisionTree out of it you will lose sharing). Also, the circular dependency only comes up if you need to use the environment to decide on an error. For example, a plain old syntactic parse error can cyclicly use an Either ErrorInfo [(String,DecisionTree)] perfectly fine (pass in fromRight env where fromRight ~(Right x) = x). It will also work even with the above approach giving the environment the type Either [(String,Either ErrorInfo DecisionTree)]. Below is code for a simplified scenario that does most of these things,<br />
<br />
<haskell><br />
module Main where<br />
import Maybe ( fromJust )<br />
import Monad<br />
<br />
main :: IO ()<br />
main = do<br />
input <- getContents<br />
length input `seq` print (fixup input)<br />
<br />
instance Monad (Either s) where<br />
return = Right<br />
m >>= f = either Left f m<br />
<br />
isLeft :: Either a b -> Bool<br />
isLeft (Left _) = True<br />
isLeft _ = False<br />
<br />
fromRight :: Either a b -> b<br />
fromRight ~(Right x) = x<br />
<br />
fixup :: String -> Either String [(String,Either String String)]<br />
fixup input = env<br />
where env = mapM (parse (fromRight env) . words) (lines input)<br />
<br />
checkedFixup :: String -> Either String [(String,String)]<br />
checkedFixup input =<br />
case fixup input of<br />
Left err -> Left err<br />
Right env -> case filter (isLeft . snd) env of<br />
[] -> Right $ map (\(n,Right v) -> (n,v)) env<br />
(_,Left err):_ -> Left err<br />
<br />
parse :: [(String,Either String String)] -> [String] -> Either String (String,Either String String)<br />
parse env ("define":name:values) = Right (name,values')<br />
where values' = liftM unwords $ mapM lookupRef values<br />
lookupRef ('*':word)<br />
= maybe (Left $ "couldn't find "++word++" in "++name)<br />
id<br />
(lookup word env)<br />
lookupRef word = Right word<br />
parse env input = Left $ "parse error with: "++unwords input<br />
</haskell><br />
checkedFixup demonstrates how you could check and recover, but since the environment is the return value neither fixup or checkedFixup quite illustrate having potentially erroneous thunks in the actual return value. Some example input,<br />
<haskell><br />
define x *y *y<br />
define y a b<br />
outputs Right [("x",Right "a b a b"),("y",Right "a b")]<br />
define x *y *y<br />
aousht<br />
define y a b<br />
outputs Left "parse error with: aousht"<br />
define x *y *z<br />
define y a b<br />
define z *w<br />
outputs Right [("x",Left "couldn't find w in z"),("y",Right "a b"),("z",Left "couldn't find w in z")]<br />
</haskell><br />
<br />
* Consider a tree Y that contains the subtree X twice. With Oleg's version, when we resolve the "X" variable we look up a (manually) delayed tree and then build X. Each subtree of Y will build it's own version of X. With the truly circular version each subtree of Y will be the same, possibly erroneous, thunk that builds X, if the thunk isn't erroneous then when it is updated both of Y's subtrees will point to the same X.<br />
<br />
-- DerekElkins<br />
<br />
<br />
[[Category:Code]]<br />
[[Category:Idioms]]</div>Hyiltizhttps://wiki.haskell.org/index.php?title=Data.Semigroup&diff=63184Data.Semigroup2020-01-15T08:36:21Z<p>Hyiltiz: /* Exponents using stimes: */ language clearup (see Talk page and Haskell IRC)</p>
<hr />
<div>The <strong><hask>Semigroup</hask></strong> represents a set with an associative binary operation. This makes a semigroup a superset of [[Data.Monoid|monoids]]. Semigoups have no other restrictions, and are a very general typeclass.<br />
<br />
<div>See also [[Data.Monoid]]: a <hask>Semigroup</hask> with an identity value.</div><br />
<br />
== Packages ==<br />
* (base) Data.Semigroup<br />
<br />
== Syntax ==<br />
<br />
<pre><br />
class Semigroup a where<br />
(<>) :: a -> a -> a<br />
sconcat :: [[Data.List.Nonempty|Nonempty]] a -> a<br />
stimes :: Integral b => b -> a -> a<br />
</pre><br />
<br />
=== Minimal Complete Definition ===<br />
<br />
<pre><br />
(<>)<br />
</pre><br />
<br />
== Description ==<br />
<br />
<div>Any datatype <hask>a</hask> which has an associative binary operation will be able to become a member of the <hask>Semigroup</hask> typeclass. An instance of <hask>Monoid a</hask> automatically satisfies the requirements of a <hask>Semigroup</hask> making <hask>Semigroup</hask> a strict superset of <hask>Monoid</hask>. The <hask>Monoid</hask> typeclass however does not enforce it's instances to already be instances of <hask>Semigroup</hask></div><br />
<br />
<div>The <hask>Semigroup</hask> is a particularly forgiving typeclass in it's requirements, and datatypes may have many instances of <hask>Semigroup</hask> as long as they have functions which satisfy the requirements.</div><br />
<br />
=== Semigroup Laws ===<br />
<br />
In addition to the class requirements above, potential instances of <hask>Semigroup</hask> must obey a single law in order to become instances:<br />
<br />
;The binary operation <hask><></hask> must be associative<br />
:<pre>(a <> b) <> c == a <> (b <> c)</pre><br />
:As long as you do not change the order of the arguments, you can insert parenthesis anywhere, and the result will be the same.<br />
<br />
For example, addition (<hask>a + (b + c) == (a + b) + c</hask>), and multiplication (<hask>a * (b * c) == (a * b) * c</hask>) satisfy this requirement. Therefore <hask><></hask> could be defined as <hask>+</hask> or <hask>*</hask> for instances of class <hask>Num a</hask>. Division (<hask>div</hask>) however, would not be a candidate as it is not associative: <hask>8 `div` (4 `div` 2) == 8 `div` 2 == 4</hask> is not equal to <hask>(8 `div` 4) `div` 2 == 2 `div` 2 == 1</hask>.<br />
<br />
<div>In essence, the <hask><></hask> function could do anything, as long as it doesn't matter where you put parenthesis.</div><br />
<br />
=== Rules for Monoids ===<br />
<br />
Instances of <hask>Monoid</hask> have to obey an additional rule:<br />
<pre>(<>) == mappend</pre><br />
This is to ensure that the instance of <hask>Monoid</hask> is equivalent to a more strict instance of <hask>Semigroup</hask>.<br />
<br />
== Methods ==<br />
<br />
<pre>(<>) :: a -> a -> a</pre><br />
:An associative binary operation.<br />
<pre>sconcat :: [[Data.List.Nonempty|Nonempty]] a -> a</pre><br />
:Take a nonempty list of type <hask>a</hask> and apply the <hask><></hask> operation to all of them to get a single result.<br />
<pre>stimes :: Integral b => b -> a -> a</pre><br />
:Given a number <hask>x</hask> and a value of type <hask>a</hask>, combine <hask>x</hask> numbers of the value <hask>a</hask> by repeatedly applying <hask><></hask>.<br />
<br />
== Examples ==<br />
<br />
=== Sum numbers using <hask><></hask>: ===<br />
<br />
<pre><br />
Sum 3 <> Sum 4 -- with the type "Sum a", "<>" becomes "+"<br />
-- returns: Sum {getSum = 7} because (3 <> 4) == (3 + 4) == 7<br />
</pre><br />
<br />
=== Exponents using <hask>stimes</hask>: ===<br />
<br />
<pre><br />
stimes 4 (Product 3) -- with the type "Product a" "<>" becomes "*"<br />
-- returns: Product {getProduct = 81} <br />
-- This is because (3 <> 3 <> 3 <> 3) == (3 * 3 * 3 * 3) == 81<br />
-- i.e. 4 copies of 3 combined via multiplication.<br />
</pre><br />
<br />
=== Test for any elements which are True in a non-empty list using <hask>sconcat</hask>: ===<br />
<br />
<pre><br />
sconcat (Any True :| [Any False, Any True, Any False])<br />
-- sconcat will apply "<>" to all of the members in a list<br />
-- returns: Any {getAny = True}<br />
-- If any elements in the list are True than the whole expression is True<br />
-- The type "Any" converts "<>" to "||"<br />
-- (True <> False <> True <> False) == (True || False || True || False) == True<br />
</pre><br />
<br />
=== Test if all elements are True in a non-empty list using <hask>sconcat</hask>: ===<br />
<br />
<pre><br />
sconcat (All True :| [All False, All True, All False])<br />
-- returns: All {getAll = False}<br />
-- If all elements in the list are True than the whole expression is True<br />
-- The type "All" converts "<>" to "&&"<br />
-- (True <> False <> True <> False) == (True && False && True && False) == True<br />
</pre><br />
<br />
== See Also ==<br />
* [[Data.Monoid]]: a special case of <hask>Semigroup</hask> with an identity element <hask>mempty</hask><br />
<br />
<br />
[[Category:Semigroup]]<br />
[[Category:Reference]]</div>Hyiltizhttps://wiki.haskell.org/index.php?title=Data.Semigroup&diff=63183Data.Semigroup2020-01-15T08:35:20Z<p>Hyiltiz: /* Methods */</p>
<hr />
<div>The <strong><hask>Semigroup</hask></strong> represents a set with an associative binary operation. This makes a semigroup a superset of [[Data.Monoid|monoids]]. Semigoups have no other restrictions, and are a very general typeclass.<br />
<br />
<div>See also [[Data.Monoid]]: a <hask>Semigroup</hask> with an identity value.</div><br />
<br />
== Packages ==<br />
* (base) Data.Semigroup<br />
<br />
== Syntax ==<br />
<br />
<pre><br />
class Semigroup a where<br />
(<>) :: a -> a -> a<br />
sconcat :: [[Data.List.Nonempty|Nonempty]] a -> a<br />
stimes :: Integral b => b -> a -> a<br />
</pre><br />
<br />
=== Minimal Complete Definition ===<br />
<br />
<pre><br />
(<>)<br />
</pre><br />
<br />
== Description ==<br />
<br />
<div>Any datatype <hask>a</hask> which has an associative binary operation will be able to become a member of the <hask>Semigroup</hask> typeclass. An instance of <hask>Monoid a</hask> automatically satisfies the requirements of a <hask>Semigroup</hask> making <hask>Semigroup</hask> a strict superset of <hask>Monoid</hask>. The <hask>Monoid</hask> typeclass however does not enforce it's instances to already be instances of <hask>Semigroup</hask></div><br />
<br />
<div>The <hask>Semigroup</hask> is a particularly forgiving typeclass in it's requirements, and datatypes may have many instances of <hask>Semigroup</hask> as long as they have functions which satisfy the requirements.</div><br />
<br />
=== Semigroup Laws ===<br />
<br />
In addition to the class requirements above, potential instances of <hask>Semigroup</hask> must obey a single law in order to become instances:<br />
<br />
;The binary operation <hask><></hask> must be associative<br />
:<pre>(a <> b) <> c == a <> (b <> c)</pre><br />
:As long as you do not change the order of the arguments, you can insert parenthesis anywhere, and the result will be the same.<br />
<br />
For example, addition (<hask>a + (b + c) == (a + b) + c</hask>), and multiplication (<hask>a * (b * c) == (a * b) * c</hask>) satisfy this requirement. Therefore <hask><></hask> could be defined as <hask>+</hask> or <hask>*</hask> for instances of class <hask>Num a</hask>. Division (<hask>div</hask>) however, would not be a candidate as it is not associative: <hask>8 `div` (4 `div` 2) == 8 `div` 2 == 4</hask> is not equal to <hask>(8 `div` 4) `div` 2 == 2 `div` 2 == 1</hask>.<br />
<br />
<div>In essence, the <hask><></hask> function could do anything, as long as it doesn't matter where you put parenthesis.</div><br />
<br />
=== Rules for Monoids ===<br />
<br />
Instances of <hask>Monoid</hask> have to obey an additional rule:<br />
<pre>(<>) == mappend</pre><br />
This is to ensure that the instance of <hask>Monoid</hask> is equivalent to a more strict instance of <hask>Semigroup</hask>.<br />
<br />
== Methods ==<br />
<br />
<pre>(<>) :: a -> a -> a</pre><br />
:An associative binary operation.<br />
<pre>sconcat :: [[Data.List.Nonempty|Nonempty]] a -> a</pre><br />
:Take a nonempty list of type <hask>a</hask> and apply the <hask><></hask> operation to all of them to get a single result.<br />
<pre>stimes :: Integral b => b -> a -> a</pre><br />
:Given a number <hask>x</hask> and a value of type <hask>a</hask>, combine <hask>x</hask> numbers of the value <hask>a</hask> by repeatedly applying <hask><></hask>.<br />
<br />
== Examples ==<br />
<br />
=== Sum numbers using <hask><></hask>: ===<br />
<br />
<pre><br />
Sum 3 <> Sum 4 -- with the type "Sum a", "<>" becomes "+"<br />
-- returns: Sum {getSum = 7} because (3 <> 4) == (3 + 4) == 7<br />
</pre><br />
<br />
=== Exponents using <hask>stimes</hask>: ===<br />
<br />
<pre><br />
stimes 4 (Product 3) -- with the type "Product a" "<>" becomes "*"<br />
-- returns: Product {getProduct = 81} <br />
-- This is because (3 <> 3 <> 3 <> 3) == (3 * 3 * 3 * 3) == 81<br />
-- i.e. 3 multiplied by itself 4 times.<br />
</pre><br />
<br />
=== Test for any elements which are True in a non-empty list using <hask>sconcat</hask>: ===<br />
<br />
<pre><br />
sconcat (Any True :| [Any False, Any True, Any False])<br />
-- sconcat will apply "<>" to all of the members in a list<br />
-- returns: Any {getAny = True}<br />
-- If any elements in the list are True than the whole expression is True<br />
-- The type "Any" converts "<>" to "||"<br />
-- (True <> False <> True <> False) == (True || False || True || False) == True<br />
</pre><br />
<br />
=== Test if all elements are True in a non-empty list using <hask>sconcat</hask>: ===<br />
<br />
<pre><br />
sconcat (All True :| [All False, All True, All False])<br />
-- returns: All {getAll = False}<br />
-- If all elements in the list are True than the whole expression is True<br />
-- The type "All" converts "<>" to "&&"<br />
-- (True <> False <> True <> False) == (True && False && True && False) == True<br />
</pre><br />
<br />
== See Also ==<br />
* [[Data.Monoid]]: a special case of <hask>Semigroup</hask> with an identity element <hask>mempty</hask><br />
<br />
<br />
[[Category:Semigroup]]<br />
[[Category:Reference]]</div>Hyiltizhttps://wiki.haskell.org/index.php?title=Data.Semigroup&diff=63182Data.Semigroup2020-01-15T08:34:49Z<p>Hyiltiz: /* Methods */ formatting</p>
<hr />
<div>The <strong><hask>Semigroup</hask></strong> represents a set with an associative binary operation. This makes a semigroup a superset of [[Data.Monoid|monoids]]. Semigoups have no other restrictions, and are a very general typeclass.<br />
<br />
<div>See also [[Data.Monoid]]: a <hask>Semigroup</hask> with an identity value.</div><br />
<br />
== Packages ==<br />
* (base) Data.Semigroup<br />
<br />
== Syntax ==<br />
<br />
<pre><br />
class Semigroup a where<br />
(<>) :: a -> a -> a<br />
sconcat :: [[Data.List.Nonempty|Nonempty]] a -> a<br />
stimes :: Integral b => b -> a -> a<br />
</pre><br />
<br />
=== Minimal Complete Definition ===<br />
<br />
<pre><br />
(<>)<br />
</pre><br />
<br />
== Description ==<br />
<br />
<div>Any datatype <hask>a</hask> which has an associative binary operation will be able to become a member of the <hask>Semigroup</hask> typeclass. An instance of <hask>Monoid a</hask> automatically satisfies the requirements of a <hask>Semigroup</hask> making <hask>Semigroup</hask> a strict superset of <hask>Monoid</hask>. The <hask>Monoid</hask> typeclass however does not enforce it's instances to already be instances of <hask>Semigroup</hask></div><br />
<br />
<div>The <hask>Semigroup</hask> is a particularly forgiving typeclass in it's requirements, and datatypes may have many instances of <hask>Semigroup</hask> as long as they have functions which satisfy the requirements.</div><br />
<br />
=== Semigroup Laws ===<br />
<br />
In addition to the class requirements above, potential instances of <hask>Semigroup</hask> must obey a single law in order to become instances:<br />
<br />
;The binary operation <hask><></hask> must be associative<br />
:<pre>(a <> b) <> c == a <> (b <> c)</pre><br />
:As long as you do not change the order of the arguments, you can insert parenthesis anywhere, and the result will be the same.<br />
<br />
For example, addition (<hask>a + (b + c) == (a + b) + c</hask>), and multiplication (<hask>a * (b * c) == (a * b) * c</hask>) satisfy this requirement. Therefore <hask><></hask> could be defined as <hask>+</hask> or <hask>*</hask> for instances of class <hask>Num a</hask>. Division (<hask>div</hask>) however, would not be a candidate as it is not associative: <hask>8 `div` (4 `div` 2) == 8 `div` 2 == 4</hask> is not equal to <hask>(8 `div` 4) `div` 2 == 2 `div` 2 == 1</hask>.<br />
<br />
<div>In essence, the <hask><></hask> function could do anything, as long as it doesn't matter where you put parenthesis.</div><br />
<br />
=== Rules for Monoids ===<br />
<br />
Instances of <hask>Monoid</hask> have to obey an additional rule:<br />
<pre>(<>) == mappend</pre><br />
This is to ensure that the instance of <hask>Monoid</hask> is equivalent to a more strict instance of <hask>Semigroup</hask>.<br />
<br />
== Methods ==<br />
<br />
<pre>(<>) :: a -> a -> a</pre><br />
:An associative binary operation.<br />
<pre>sconcat :: [[Data.List.Nonempty|Nonempty]] a -> a</pre><br />
:Take a nonempty list of type <hask>a</hask> and apply the <hask><></hask> operation to all of them to get a single result.<br />
<pre>stimes :: Integral b => b -> a -> a</pre><br />
:Given a number <hask>x</hask> and a value of type <hask>a</hask>, combine <hask>x</hask> times of the value <hask>a</hask> by repeatedly applying <hask><></hask>.<br />
<br />
== Examples ==<br />
<br />
=== Sum numbers using <hask><></hask>: ===<br />
<br />
<pre><br />
Sum 3 <> Sum 4 -- with the type "Sum a", "<>" becomes "+"<br />
-- returns: Sum {getSum = 7} because (3 <> 4) == (3 + 4) == 7<br />
</pre><br />
<br />
=== Exponents using <hask>stimes</hask>: ===<br />
<br />
<pre><br />
stimes 4 (Product 3) -- with the type "Product a" "<>" becomes "*"<br />
-- returns: Product {getProduct = 81} <br />
-- This is because (3 <> 3 <> 3 <> 3) == (3 * 3 * 3 * 3) == 81<br />
-- i.e. 3 multiplied by itself 4 times.<br />
</pre><br />
<br />
=== Test for any elements which are True in a non-empty list using <hask>sconcat</hask>: ===<br />
<br />
<pre><br />
sconcat (Any True :| [Any False, Any True, Any False])<br />
-- sconcat will apply "<>" to all of the members in a list<br />
-- returns: Any {getAny = True}<br />
-- If any elements in the list are True than the whole expression is True<br />
-- The type "Any" converts "<>" to "||"<br />
-- (True <> False <> True <> False) == (True || False || True || False) == True<br />
</pre><br />
<br />
=== Test if all elements are True in a non-empty list using <hask>sconcat</hask>: ===<br />
<br />
<pre><br />
sconcat (All True :| [All False, All True, All False])<br />
-- returns: All {getAll = False}<br />
-- If all elements in the list are True than the whole expression is True<br />
-- The type "All" converts "<>" to "&&"<br />
-- (True <> False <> True <> False) == (True && False && True && False) == True<br />
</pre><br />
<br />
== See Also ==<br />
* [[Data.Monoid]]: a special case of <hask>Semigroup</hask> with an identity element <hask>mempty</hask><br />
<br />
<br />
[[Category:Semigroup]]<br />
[[Category:Reference]]</div>Hyiltizhttps://wiki.haskell.org/index.php?title=Data.Semigroup&diff=63181Data.Semigroup2020-01-15T08:34:10Z<p>Hyiltiz: /* Methods */ clear up confusing wording (see Talk page and Haskell IRC)</p>
<hr />
<div>The <strong><hask>Semigroup</hask></strong> represents a set with an associative binary operation. This makes a semigroup a superset of [[Data.Monoid|monoids]]. Semigoups have no other restrictions, and are a very general typeclass.<br />
<br />
<div>See also [[Data.Monoid]]: a <hask>Semigroup</hask> with an identity value.</div><br />
<br />
== Packages ==<br />
* (base) Data.Semigroup<br />
<br />
== Syntax ==<br />
<br />
<pre><br />
class Semigroup a where<br />
(<>) :: a -> a -> a<br />
sconcat :: [[Data.List.Nonempty|Nonempty]] a -> a<br />
stimes :: Integral b => b -> a -> a<br />
</pre><br />
<br />
=== Minimal Complete Definition ===<br />
<br />
<pre><br />
(<>)<br />
</pre><br />
<br />
== Description ==<br />
<br />
<div>Any datatype <hask>a</hask> which has an associative binary operation will be able to become a member of the <hask>Semigroup</hask> typeclass. An instance of <hask>Monoid a</hask> automatically satisfies the requirements of a <hask>Semigroup</hask> making <hask>Semigroup</hask> a strict superset of <hask>Monoid</hask>. The <hask>Monoid</hask> typeclass however does not enforce it's instances to already be instances of <hask>Semigroup</hask></div><br />
<br />
<div>The <hask>Semigroup</hask> is a particularly forgiving typeclass in it's requirements, and datatypes may have many instances of <hask>Semigroup</hask> as long as they have functions which satisfy the requirements.</div><br />
<br />
=== Semigroup Laws ===<br />
<br />
In addition to the class requirements above, potential instances of <hask>Semigroup</hask> must obey a single law in order to become instances:<br />
<br />
;The binary operation <hask><></hask> must be associative<br />
:<pre>(a <> b) <> c == a <> (b <> c)</pre><br />
:As long as you do not change the order of the arguments, you can insert parenthesis anywhere, and the result will be the same.<br />
<br />
For example, addition (<hask>a + (b + c) == (a + b) + c</hask>), and multiplication (<hask>a * (b * c) == (a * b) * c</hask>) satisfy this requirement. Therefore <hask><></hask> could be defined as <hask>+</hask> or <hask>*</hask> for instances of class <hask>Num a</hask>. Division (<hask>div</hask>) however, would not be a candidate as it is not associative: <hask>8 `div` (4 `div` 2) == 8 `div` 2 == 4</hask> is not equal to <hask>(8 `div` 4) `div` 2 == 2 `div` 2 == 1</hask>.<br />
<br />
<div>In essence, the <hask><></hask> function could do anything, as long as it doesn't matter where you put parenthesis.</div><br />
<br />
=== Rules for Monoids ===<br />
<br />
Instances of <hask>Monoid</hask> have to obey an additional rule:<br />
<pre>(<>) == mappend</pre><br />
This is to ensure that the instance of <hask>Monoid</hask> is equivalent to a more strict instance of <hask>Semigroup</hask>.<br />
<br />
== Methods ==<br />
<br />
<pre>(<>) :: a -> a -> a</pre><br />
:An associative binary operation.<br />
<pre>sconcat :: [[Data.List.Nonempty|Nonempty]] a -> a</pre><br />
:Take a nonempty list of type <hask>a</hask> and apply the <hask><></hask> operation to all of them to get a single result.<br />
<pre>stimes :: Integral b => b -> a -> a</pre><br />
:Given a number <hask>x</hask> and a value of type <hask>a</hask>, combine `x` times of the value `a` by repeatedly applying <hask><></hask>.<br />
<br />
== Examples ==<br />
<br />
=== Sum numbers using <hask><></hask>: ===<br />
<br />
<pre><br />
Sum 3 <> Sum 4 -- with the type "Sum a", "<>" becomes "+"<br />
-- returns: Sum {getSum = 7} because (3 <> 4) == (3 + 4) == 7<br />
</pre><br />
<br />
=== Exponents using <hask>stimes</hask>: ===<br />
<br />
<pre><br />
stimes 4 (Product 3) -- with the type "Product a" "<>" becomes "*"<br />
-- returns: Product {getProduct = 81} <br />
-- This is because (3 <> 3 <> 3 <> 3) == (3 * 3 * 3 * 3) == 81<br />
-- i.e. 3 multiplied by itself 4 times.<br />
</pre><br />
<br />
=== Test for any elements which are True in a non-empty list using <hask>sconcat</hask>: ===<br />
<br />
<pre><br />
sconcat (Any True :| [Any False, Any True, Any False])<br />
-- sconcat will apply "<>" to all of the members in a list<br />
-- returns: Any {getAny = True}<br />
-- If any elements in the list are True than the whole expression is True<br />
-- The type "Any" converts "<>" to "||"<br />
-- (True <> False <> True <> False) == (True || False || True || False) == True<br />
</pre><br />
<br />
=== Test if all elements are True in a non-empty list using <hask>sconcat</hask>: ===<br />
<br />
<pre><br />
sconcat (All True :| [All False, All True, All False])<br />
-- returns: All {getAll = False}<br />
-- If all elements in the list are True than the whole expression is True<br />
-- The type "All" converts "<>" to "&&"<br />
-- (True <> False <> True <> False) == (True && False && True && False) == True<br />
</pre><br />
<br />
== See Also ==<br />
* [[Data.Monoid]]: a special case of <hask>Semigroup</hask> with an identity element <hask>mempty</hask><br />
<br />
<br />
[[Category:Semigroup]]<br />
[[Category:Reference]]</div>Hyiltizhttps://wiki.haskell.org/index.php?title=Talk:Data.Semigroup&diff=63180Talk:Data.Semigroup2020-01-15T08:04:23Z<p>Hyiltiz: a counting error?</p>
<hr />
<div>The page says `stimes` applies the binary operation `x` times, while in fact, the binary operation is applied `x-1` times to `x` elements in total. In the example `3 * 3 * 3 * 3`, `*` is applied three times (not four), to four elements. Here is what the page currently (Jan, 2020) says:<br />
<br />
<pre>stimes :: Integral b => b -> a -> a</pre><br />
:Given a number <hask>x</hask> and a value of type <hask>a</hask>, apply <hask><></hask> to the value <hask>x</hask> times.<br />
<br />
<pre><br />
stimes 4 (Product 3) -- with the type "Product a" "<>" becomes "*"<br />
-- returns: Product {getProduct = 81} <br />
-- This is because (3 <> 3 <> 3 <> 3) == (3 * 3 * 3 * 3) == 81<br />
-- i.e. 3 multiplied by itself 4 times.<br />
</pre></div>Hyiltiz